fix(mcp): split categories by server and preserve toggle states
This commit is contained in:
parent
2c75bb01a0
commit
3c1773dc01
@ -63,7 +63,7 @@ from modules.skills_manager import (
|
|||||||
)
|
)
|
||||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||||
from modules.custom_tool_executor import CustomToolExecutor
|
from modules.custom_tool_executor import CustomToolExecutor
|
||||||
from modules.mcp_server_registry import build_default_mcp_category
|
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from config.limits import THINKING_FAST_INTERVAL
|
from config.limits import THINKING_FAST_INTERVAL
|
||||||
@ -196,22 +196,37 @@ class MainTerminalToolsDefinitionMixin:
|
|||||||
def _build_mcp_tools(self) -> List[Dict]:
|
def _build_mcp_tools(self) -> List[Dict]:
|
||||||
if not getattr(self, "mcp_tools_enabled", False):
|
if not getattr(self, "mcp_tools_enabled", False):
|
||||||
if hasattr(self, "tool_categories_map") and "mcp" in self.tool_categories_map:
|
if hasattr(self, "tool_categories_map") and "mcp" in self.tool_categories_map:
|
||||||
self.tool_categories_map["mcp"].tools = []
|
self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
|
||||||
|
stale_keys = [
|
||||||
|
key for key in list(getattr(self, "tool_categories_map", {}).keys())
|
||||||
|
if isinstance(key, str) and key.startswith("mcp_server__")
|
||||||
|
]
|
||||||
|
for key in stale_keys:
|
||||||
|
self.tool_categories_map.pop(key, None)
|
||||||
|
self.tool_category_states.pop(key, None)
|
||||||
|
self._refresh_disabled_tools()
|
||||||
self.mcp_tool_alias_map = {}
|
self.mcp_tool_alias_map = {}
|
||||||
return []
|
return []
|
||||||
if self._is_mcp_disabled_in_docker_mode():
|
if self._is_mcp_disabled_in_docker_mode():
|
||||||
self.mcp_tool_alias_map = {}
|
self.mcp_tool_alias_map = {}
|
||||||
mcp_category_tools = ["list_mcp_servers"]
|
|
||||||
if "mcp" in self.tool_categories_map:
|
if "mcp" in self.tool_categories_map:
|
||||||
self.tool_categories_map["mcp"].tools = mcp_category_tools
|
self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
|
||||||
elif getattr(self, "mcp_tools_enabled", False):
|
elif getattr(self, "mcp_tools_enabled", False):
|
||||||
default_mcp_cat = build_default_mcp_category()
|
default_mcp_cat = build_default_mcp_category()
|
||||||
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
||||||
label=default_mcp_cat["label"],
|
label=default_mcp_cat["label"],
|
||||||
tools=mcp_category_tools,
|
tools=["list_mcp_servers"],
|
||||||
default_enabled=True,
|
default_enabled=True,
|
||||||
silent_when_disabled=False,
|
silent_when_disabled=False,
|
||||||
)
|
)
|
||||||
|
stale_keys = [
|
||||||
|
key for key in list(getattr(self, "tool_categories_map", {}).keys())
|
||||||
|
if isinstance(key, str) and key.startswith("mcp_server__")
|
||||||
|
]
|
||||||
|
for key in stale_keys:
|
||||||
|
self.tool_categories_map.pop(key, None)
|
||||||
|
self.tool_category_states.pop(key, None)
|
||||||
|
self._refresh_disabled_tools()
|
||||||
return []
|
return []
|
||||||
# 类别层面的禁用优先生效(即使策略里尚未填充具体工具列表)
|
# 类别层面的禁用优先生效(即使策略里尚未填充具体工具列表)
|
||||||
mcp_cat = getattr(self, "tool_categories_map", {}).get("mcp")
|
mcp_cat = getattr(self, "tool_categories_map", {}).get("mcp")
|
||||||
@ -222,7 +237,15 @@ class MainTerminalToolsDefinitionMixin:
|
|||||||
mcp_enabled = forced
|
mcp_enabled = forced
|
||||||
if not mcp_enabled:
|
if not mcp_enabled:
|
||||||
self.mcp_tool_alias_map = {}
|
self.mcp_tool_alias_map = {}
|
||||||
self.tool_categories_map["mcp"].tools = []
|
self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
|
||||||
|
stale_keys = [
|
||||||
|
key for key in list(getattr(self, "tool_categories_map", {}).keys())
|
||||||
|
if isinstance(key, str) and key.startswith("mcp_server__")
|
||||||
|
]
|
||||||
|
for key in stale_keys:
|
||||||
|
self.tool_categories_map.pop(key, None)
|
||||||
|
self.tool_category_states.pop(key, None)
|
||||||
|
self._refresh_disabled_tools()
|
||||||
return []
|
return []
|
||||||
manager = getattr(self, "mcp_client_manager", None)
|
manager = getattr(self, "mcp_client_manager", None)
|
||||||
if manager is None:
|
if manager is None:
|
||||||
@ -238,22 +261,97 @@ class MainTerminalToolsDefinitionMixin:
|
|||||||
tool_defs, alias_map = [], {}
|
tool_defs, alias_map = [], {}
|
||||||
|
|
||||||
# 保存映射,供执行层按 alias 分发到具体 server/tool
|
# 保存映射,供执行层按 alias 分发到具体 server/tool
|
||||||
self.mcp_tool_alias_map = alias_map or {}
|
alias_map = alias_map or {}
|
||||||
mcp_category_tools = ["list_mcp_servers", *list((alias_map or {}).keys())]
|
|
||||||
|
|
||||||
# 同步 mcp 分类列表,避免策略页显示旧数据
|
|
||||||
if "mcp" in self.tool_categories_map:
|
if "mcp" in self.tool_categories_map:
|
||||||
self.tool_categories_map["mcp"].tools = mcp_category_tools
|
self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
|
||||||
elif getattr(self, "mcp_tools_enabled", False):
|
elif getattr(self, "mcp_tools_enabled", False):
|
||||||
default_mcp_cat = build_default_mcp_category()
|
default_mcp_cat = build_default_mcp_category()
|
||||||
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
||||||
label=default_mcp_cat["label"],
|
label=default_mcp_cat["label"],
|
||||||
tools=mcp_category_tools,
|
tools=["list_mcp_servers"],
|
||||||
default_enabled=True,
|
default_enabled=True,
|
||||||
silent_when_disabled=False,
|
silent_when_disabled=False,
|
||||||
)
|
)
|
||||||
|
server_aliases: Dict[str, List[str]] = {}
|
||||||
|
for alias, binding in alias_map.items():
|
||||||
|
sid = str(getattr(binding, "server_id", "") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
server_aliases.setdefault(sid, []).append(alias)
|
||||||
|
|
||||||
return tool_defs
|
enabled_servers = []
|
||||||
|
try:
|
||||||
|
enabled_servers = list(self.mcp_server_registry.list_enabled_servers())
|
||||||
|
except Exception:
|
||||||
|
enabled_servers = []
|
||||||
|
|
||||||
|
active_server_category_ids = set()
|
||||||
|
for server in enabled_servers:
|
||||||
|
sid = str(server.get("id") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
cat_id = MCPServerRegistry.make_server_category_id(sid)
|
||||||
|
active_server_category_ids.add(cat_id)
|
||||||
|
aliases = list(server_aliases.get(sid, []))
|
||||||
|
existing = self.tool_categories_map.get(cat_id)
|
||||||
|
label = MCPServerRegistry.make_server_category_label(server)
|
||||||
|
if existing is not None:
|
||||||
|
existing.label = label
|
||||||
|
existing.tools = aliases
|
||||||
|
else:
|
||||||
|
self.tool_categories_map[cat_id] = type(next(iter(TOOL_CATEGORIES.values())))(
|
||||||
|
label=label,
|
||||||
|
tools=aliases,
|
||||||
|
default_enabled=True,
|
||||||
|
silent_when_disabled=False,
|
||||||
|
)
|
||||||
|
if cat_id not in self.tool_category_states:
|
||||||
|
self.tool_category_states[cat_id] = True
|
||||||
|
|
||||||
|
stale_keys = [
|
||||||
|
key for key in list(self.tool_categories_map.keys())
|
||||||
|
if isinstance(key, str)
|
||||||
|
and key.startswith("mcp_server__")
|
||||||
|
and key not in active_server_category_ids
|
||||||
|
]
|
||||||
|
for key in stale_keys:
|
||||||
|
self.tool_categories_map.pop(key, None)
|
||||||
|
self.tool_category_states.pop(key, None)
|
||||||
|
|
||||||
|
if hasattr(self, "_prune_runtime_tool_overrides"):
|
||||||
|
self._prune_runtime_tool_overrides()
|
||||||
|
if hasattr(self, "_apply_runtime_tool_overrides"):
|
||||||
|
self._apply_runtime_tool_overrides()
|
||||||
|
|
||||||
|
filtered_tools: List[Dict[str, Any]] = []
|
||||||
|
filtered_alias_map: Dict[str, Any] = {}
|
||||||
|
for tool_def in tool_defs:
|
||||||
|
alias = str(((tool_def or {}).get("function") or {}).get("name") or "").strip()
|
||||||
|
if not alias:
|
||||||
|
continue
|
||||||
|
binding = alias_map.get(alias)
|
||||||
|
if binding is None:
|
||||||
|
continue
|
||||||
|
sid = str(getattr(binding, "server_id", "") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
cat_id = MCPServerRegistry.make_server_category_id(sid)
|
||||||
|
cat = self.tool_categories_map.get(cat_id)
|
||||||
|
cat_enabled = self.tool_category_states.get(
|
||||||
|
cat_id,
|
||||||
|
getattr(cat, "default_enabled", True) if cat is not None else True,
|
||||||
|
)
|
||||||
|
forced = getattr(self, "admin_forced_category_states", {}).get(cat_id)
|
||||||
|
if isinstance(forced, bool):
|
||||||
|
cat_enabled = forced
|
||||||
|
if not cat_enabled:
|
||||||
|
continue
|
||||||
|
filtered_tools.append(tool_def)
|
||||||
|
filtered_alias_map[alias] = binding
|
||||||
|
|
||||||
|
self.mcp_tool_alias_map = filtered_alias_map
|
||||||
|
self._refresh_disabled_tools()
|
||||||
|
return filtered_tools
|
||||||
|
|
||||||
def define_tools(self) -> List[Dict]:
|
def define_tools(self) -> List[Dict]:
|
||||||
"""定义可用工具(添加确认工具)"""
|
"""定义可用工具(添加确认工具)"""
|
||||||
|
|||||||
@ -87,6 +87,34 @@ DISABLE_LENGTH_CHECK = True
|
|||||||
PERMISSION_MODES = {"readonly", "approval", "unrestricted"}
|
PERMISSION_MODES = {"readonly", "approval", "unrestricted"}
|
||||||
|
|
||||||
class MainTerminalToolsPolicyMixin:
|
class MainTerminalToolsPolicyMixin:
|
||||||
|
def _ensure_runtime_tool_overrides(self) -> Dict[str, bool]:
|
||||||
|
overrides = getattr(self, "runtime_tool_category_overrides", None)
|
||||||
|
if not isinstance(overrides, dict):
|
||||||
|
overrides = {}
|
||||||
|
self.runtime_tool_category_overrides = overrides
|
||||||
|
return overrides
|
||||||
|
|
||||||
|
def _prune_runtime_tool_overrides(self) -> None:
|
||||||
|
overrides = self._ensure_runtime_tool_overrides()
|
||||||
|
valid_keys = set(getattr(self, "tool_categories_map", {}).keys())
|
||||||
|
for key in list(overrides.keys()):
|
||||||
|
if key not in valid_keys:
|
||||||
|
overrides.pop(key, None)
|
||||||
|
|
||||||
|
def _apply_runtime_tool_overrides(self) -> None:
|
||||||
|
overrides = self._ensure_runtime_tool_overrides()
|
||||||
|
if not overrides:
|
||||||
|
return
|
||||||
|
categories = getattr(self, "tool_categories_map", {}) or {}
|
||||||
|
for key, value in list(overrides.items()):
|
||||||
|
if key not in categories:
|
||||||
|
overrides.pop(key, None)
|
||||||
|
continue
|
||||||
|
forced = getattr(self, "admin_forced_category_states", {}).get(key)
|
||||||
|
if isinstance(forced, bool):
|
||||||
|
continue
|
||||||
|
self.tool_category_states[key] = bool(value)
|
||||||
|
|
||||||
def apply_personalization_preferences(self, config: Optional[Dict[str, Any]] = None):
|
def apply_personalization_preferences(self, config: Optional[Dict[str, Any]] = None):
|
||||||
"""Apply persisted personalization settings that affect runtime behavior."""
|
"""Apply persisted personalization settings that affect runtime behavior."""
|
||||||
try:
|
try:
|
||||||
@ -141,6 +169,8 @@ class MainTerminalToolsPolicyMixin:
|
|||||||
# Reset category states to defaults before applying overrides
|
# Reset category states to defaults before applying overrides
|
||||||
for key, category in self.tool_categories_map.items():
|
for key, category in self.tool_categories_map.items():
|
||||||
self.tool_category_states[key] = False if key in disabled_categories else category.default_enabled
|
self.tool_category_states[key] = False if key in disabled_categories else category.default_enabled
|
||||||
|
self._prune_runtime_tool_overrides()
|
||||||
|
self._apply_runtime_tool_overrides()
|
||||||
self._refresh_disabled_tools()
|
self._refresh_disabled_tools()
|
||||||
|
|
||||||
# 默认模型偏好(优先应用,再处理运行模式)
|
# 默认模型偏好(优先应用,再处理运行模式)
|
||||||
@ -205,7 +235,9 @@ class MainTerminalToolsPolicyMixin:
|
|||||||
forced = self.admin_forced_category_states.get(category)
|
forced = self.admin_forced_category_states.get(category)
|
||||||
if isinstance(forced, bool) and forced != enabled:
|
if isinstance(forced, bool) and forced != enabled:
|
||||||
raise ValueError("该类别被管理员强制为启用/禁用,无法修改")
|
raise ValueError("该类别被管理员强制为启用/禁用,无法修改")
|
||||||
self.tool_category_states[category] = bool(enabled)
|
final_enabled = bool(enabled)
|
||||||
|
self.tool_category_states[category] = final_enabled
|
||||||
|
self._ensure_runtime_tool_overrides()[category] = final_enabled
|
||||||
self._refresh_disabled_tools()
|
self._refresh_disabled_tools()
|
||||||
|
|
||||||
def set_admin_policy(
|
def set_admin_policy(
|
||||||
@ -229,10 +261,12 @@ class MainTerminalToolsPolicyMixin:
|
|||||||
default_mcp_cat = build_default_mcp_category()
|
default_mcp_cat = build_default_mcp_category()
|
||||||
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
|
||||||
label=default_mcp_cat["label"],
|
label=default_mcp_cat["label"],
|
||||||
tools=[],
|
tools=["list_mcp_servers"],
|
||||||
default_enabled=True,
|
default_enabled=True,
|
||||||
silent_when_disabled=False,
|
silent_when_disabled=False,
|
||||||
)
|
)
|
||||||
|
elif "mcp" in self.tool_categories_map:
|
||||||
|
self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
|
||||||
# 重新构建启用状态映射,保留已有值
|
# 重新构建启用状态映射,保留已有值
|
||||||
new_states: Dict[str, bool] = {}
|
new_states: Dict[str, bool] = {}
|
||||||
for key, cat in self.tool_categories_map.items():
|
for key, cat in self.tool_categories_map.items():
|
||||||
@ -248,6 +282,8 @@ class MainTerminalToolsPolicyMixin:
|
|||||||
|
|
||||||
self.admin_forced_category_states = forced_category_states or {}
|
self.admin_forced_category_states = forced_category_states or {}
|
||||||
self.admin_disabled_models = disabled_models or []
|
self.admin_disabled_models = disabled_models or []
|
||||||
|
self._prune_runtime_tool_overrides()
|
||||||
|
self._apply_runtime_tool_overrides()
|
||||||
self._refresh_disabled_tools()
|
self._refresh_disabled_tools()
|
||||||
|
|
||||||
def get_tool_settings_snapshot(self) -> List[Dict[str, object]]:
|
def get_tool_settings_snapshot(self) -> List[Dict[str, object]]:
|
||||||
|
|||||||
@ -172,13 +172,23 @@ def _collect_categories_with_overrides(overrides: Dict[str, Any]) -> Dict[str, D
|
|||||||
# 注入 MCP 工具分类(动态)
|
# 注入 MCP 工具分类(动态)
|
||||||
mcp_cat = build_default_mcp_category()
|
mcp_cat = build_default_mcp_category()
|
||||||
mcp_cat_id = mcp_cat.get("id", "mcp")
|
mcp_cat_id = mcp_cat.get("id", "mcp")
|
||||||
mcp_tools = ["list_mcp_servers", *mcp_registry.build_cached_alias_list()]
|
|
||||||
base[mcp_cat_id] = {
|
base[mcp_cat_id] = {
|
||||||
"label": mcp_cat.get("label", "MCP 工具"),
|
"label": mcp_cat.get("label", "MCP 工具"),
|
||||||
"tools": mcp_tools,
|
"tools": ["list_mcp_servers"],
|
||||||
"default_enabled": True,
|
"default_enabled": True,
|
||||||
"silent_when_disabled": False,
|
"silent_when_disabled": False,
|
||||||
}
|
}
|
||||||
|
for server in mcp_registry.list_enabled_servers():
|
||||||
|
sid = str(server.get("id") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
cat_id = MCPServerRegistry.make_server_category_id(sid)
|
||||||
|
base[cat_id] = {
|
||||||
|
"label": MCPServerRegistry.make_server_category_label(server),
|
||||||
|
"tools": mcp_registry.build_cached_alias_list(server_id=sid),
|
||||||
|
"default_enabled": True,
|
||||||
|
"silent_when_disabled": False,
|
||||||
|
}
|
||||||
|
|
||||||
remove_ids = set(overrides.get("remove_categories") or [])
|
remove_ids = set(overrides.get("remove_categories") or [])
|
||||||
for rid in remove_ids:
|
for rid in remove_ids:
|
||||||
@ -200,6 +210,33 @@ def _collect_categories_with_overrides(overrides: Dict[str, Any]) -> Dict[str, D
|
|||||||
"default_enabled": default_enabled,
|
"default_enabled": default_enabled,
|
||||||
"silent_when_disabled": bool(payload.get("silent_when_disabled", False)),
|
"silent_when_disabled": bool(payload.get("silent_when_disabled", False)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# MCP 分类由运行时动态决定:基础 mcp 仅保留原生 list_mcp_servers,
|
||||||
|
# 每个 mcp_server__* 分类独立承载该服务别名工具,避免旧策略覆盖造成错乱。
|
||||||
|
base["mcp"] = {
|
||||||
|
**(base.get("mcp") or {}),
|
||||||
|
"label": (base.get("mcp") or {}).get("label") or mcp_cat.get("label", "MCP 工具"),
|
||||||
|
"tools": ["list_mcp_servers"],
|
||||||
|
"default_enabled": bool((base.get("mcp") or {}).get("default_enabled", True)),
|
||||||
|
"silent_when_disabled": bool((base.get("mcp") or {}).get("silent_when_disabled", False)),
|
||||||
|
}
|
||||||
|
active_server_categories = set()
|
||||||
|
for server in mcp_registry.list_enabled_servers():
|
||||||
|
sid = str(server.get("id") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
cat_id = MCPServerRegistry.make_server_category_id(sid)
|
||||||
|
active_server_categories.add(cat_id)
|
||||||
|
existing = base.get(cat_id) or {}
|
||||||
|
base[cat_id] = {
|
||||||
|
"label": MCPServerRegistry.make_server_category_label(server),
|
||||||
|
"tools": mcp_registry.build_cached_alias_list(server_id=sid),
|
||||||
|
"default_enabled": bool(existing.get("default_enabled", True)),
|
||||||
|
"silent_when_disabled": bool(existing.get("silent_when_disabled", False)),
|
||||||
|
}
|
||||||
|
for cid in list(base.keys()):
|
||||||
|
if isinstance(cid, str) and cid.startswith("mcp_server__") and cid not in active_server_categories:
|
||||||
|
base.pop(cid, None)
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
@ -267,10 +304,21 @@ def describe_defaults() -> Dict[str, Any]:
|
|||||||
mcp_cat_id = mcp_cat.get("id", "mcp")
|
mcp_cat_id = mcp_cat.get("id", "mcp")
|
||||||
categories[mcp_cat_id] = {
|
categories[mcp_cat_id] = {
|
||||||
"label": mcp_cat.get("label", "MCP 工具"),
|
"label": mcp_cat.get("label", "MCP 工具"),
|
||||||
"tools": ["list_mcp_servers", *mcp_registry.build_cached_alias_list()],
|
"tools": ["list_mcp_servers"],
|
||||||
"default_enabled": True,
|
"default_enabled": True,
|
||||||
"silent_when_disabled": False,
|
"silent_when_disabled": False,
|
||||||
}
|
}
|
||||||
|
for server in mcp_registry.list_enabled_servers():
|
||||||
|
sid = str(server.get("id") or "").strip()
|
||||||
|
if not sid:
|
||||||
|
continue
|
||||||
|
cat_id = MCPServerRegistry.make_server_category_id(sid)
|
||||||
|
categories[cat_id] = {
|
||||||
|
"label": MCPServerRegistry.make_server_category_label(server),
|
||||||
|
"tools": mcp_registry.build_cached_alias_list(server_id=sid),
|
||||||
|
"default_enabled": True,
|
||||||
|
"silent_when_disabled": False,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"categories": categories,
|
"categories": categories,
|
||||||
|
|||||||
@ -106,6 +106,24 @@ class MCPServerRegistry:
|
|||||||
remain = max(8, max_len - len(prefix))
|
remain = max(8, max_len - len(prefix))
|
||||||
return f"{prefix}{tid[:remain]}"
|
return f"{prefix}{tid[:remain]}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_server_category_id(cls, server_id: str, *, max_len: int = 64) -> str:
|
||||||
|
sid = cls._safe_slug(server_id, "server")
|
||||||
|
prefix = "mcp_server__"
|
||||||
|
remain = max(8, max_len - len(prefix))
|
||||||
|
return f"{prefix}{sid[:remain]}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_server_category_label(cls, server: Any) -> str:
|
||||||
|
if isinstance(server, dict):
|
||||||
|
name = str(server.get("name") or server.get("id") or "").strip()
|
||||||
|
sid = str(server.get("id") or "").strip()
|
||||||
|
else:
|
||||||
|
name = str(server or "").strip()
|
||||||
|
sid = ""
|
||||||
|
target = name or sid or "未命名服务"
|
||||||
|
return f"MCP · {target}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_default_mcp_category() -> Dict[str, Any]:
|
def build_default_mcp_category() -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -605,11 +605,15 @@ def _sanitize_tool_categories(value: Any, allowed: set) -> list:
|
|||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
return []
|
return []
|
||||||
result = []
|
result = []
|
||||||
|
dynamic_prefixes = ("mcp_server__",)
|
||||||
for item in value:
|
for item in value:
|
||||||
if not isinstance(item, str):
|
if not isinstance(item, str):
|
||||||
continue
|
continue
|
||||||
candidate = item.strip()
|
candidate = item.strip()
|
||||||
if not candidate or candidate not in allowed:
|
if not candidate:
|
||||||
|
continue
|
||||||
|
dynamic_ok = candidate == "custom" or any(candidate.startswith(prefix) for prefix in dynamic_prefixes)
|
||||||
|
if candidate not in allowed and not dynamic_ok:
|
||||||
continue
|
continue
|
||||||
if candidate not in result:
|
if candidate not in result:
|
||||||
result.append(candidate)
|
result.append(candidate)
|
||||||
|
|||||||
@ -149,6 +149,47 @@ def get_user_resources(username: Optional[str] = None, workspace_id: Optional[st
|
|||||||
terminal.user_role = "admin"
|
terminal.user_role = "admin"
|
||||||
if has_request_context():
|
if has_request_context():
|
||||||
session['workspace_id'] = getattr(workspace, "workspace_id", None)
|
session['workspace_id'] = getattr(workspace, "workspace_id", None)
|
||||||
|
|
||||||
|
# 宿主机模式同样需要应用管理员策略(否则前端工具菜单会退化成静态基础分类)
|
||||||
|
try:
|
||||||
|
from core.tool_config import ToolCategory
|
||||||
|
from modules import admin_policy_manager
|
||||||
|
|
||||||
|
record = get_current_user_record()
|
||||||
|
role = get_current_user_role(record) if record else "admin"
|
||||||
|
invite_code = getattr(record, "invite_code", None) if record else None
|
||||||
|
policy = admin_policy_manager.get_effective_policy(
|
||||||
|
record.username if record else username,
|
||||||
|
role,
|
||||||
|
invite_code,
|
||||||
|
)
|
||||||
|
categories_map = {
|
||||||
|
cid: ToolCategory(
|
||||||
|
label=cat.get("label") or cid,
|
||||||
|
tools=list(cat.get("tools") or []),
|
||||||
|
default_enabled=bool(cat.get("default_enabled", True)),
|
||||||
|
silent_when_disabled=bool(cat.get("silent_when_disabled", False)),
|
||||||
|
)
|
||||||
|
for cid, cat in policy.get("categories", {}).items()
|
||||||
|
}
|
||||||
|
forced_states = policy.get("forced_category_states") or {}
|
||||||
|
disabled_models = policy.get("disabled_models") or []
|
||||||
|
terminal.set_admin_policy(categories_map, forced_states, disabled_models)
|
||||||
|
terminal.admin_policy_ui_blocks = policy.get("ui_blocks") or {}
|
||||||
|
terminal.admin_policy_version = policy.get("updated_at")
|
||||||
|
if terminal.model_key in disabled_models:
|
||||||
|
for candidate in get_registered_model_keys(visible_only=True):
|
||||||
|
if candidate not in disabled_models:
|
||||||
|
try:
|
||||||
|
terminal.set_model(candidate)
|
||||||
|
if has_request_context():
|
||||||
|
session["model_key"] = terminal.model_key
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
except Exception as exc:
|
||||||
|
debug_log(f"[admin_policy][host_mode] 应用失败: {exc}")
|
||||||
|
|
||||||
_ensure_workspace_skills_synced(terminal, workspace)
|
_ensure_workspace_skills_synced(terminal, workspace)
|
||||||
return terminal, workspace
|
return terminal, workspace
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,9 @@ import { debugLog } from './common';
|
|||||||
|
|
||||||
export const toolingMethods = {
|
export const toolingMethods = {
|
||||||
toolCategoryIcon(categoryId) {
|
toolCategoryIcon(categoryId) {
|
||||||
|
if (typeof categoryId === 'string' && categoryId.startsWith('mcp_server__')) {
|
||||||
|
return this.toolCategoryIcons.mcp || 'settings';
|
||||||
|
}
|
||||||
return this.toolCategoryIcons[categoryId] || 'settings';
|
return this.toolCategoryIcons[categoryId] || 'settings';
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -142,13 +145,34 @@ export const toolingMethods = {
|
|||||||
locked: !!item.locked,
|
locked: !!item.locked,
|
||||||
locked_state: typeof item.locked_state === 'boolean' ? item.locked_state : null
|
locked_state: typeof item.locked_state === 'boolean' ? item.locked_state : null
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let ordered = normalized;
|
||||||
|
const mcpServerCategories = normalized
|
||||||
|
.filter((item) => typeof item.id === 'string' && item.id.startsWith('mcp_server__'))
|
||||||
|
.sort((a, b) => String(a.label || '').localeCompare(String(b.label || '')));
|
||||||
|
if (mcpServerCategories.length > 0) {
|
||||||
|
const nonMcpServer = normalized.filter(
|
||||||
|
(item) => !(typeof item.id === 'string' && item.id.startsWith('mcp_server__'))
|
||||||
|
);
|
||||||
|
const mcpIndex = nonMcpServer.findIndex((item) => item.id === 'mcp');
|
||||||
|
if (mcpIndex >= 0) {
|
||||||
|
ordered = [
|
||||||
|
...nonMcpServer.slice(0, mcpIndex + 1),
|
||||||
|
...mcpServerCategories,
|
||||||
|
...nonMcpServer.slice(mcpIndex + 1)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
ordered = [...mcpServerCategories, ...nonMcpServer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debugLog('[ToolSettings] Snapshot applied', {
|
debugLog('[ToolSettings] Snapshot applied', {
|
||||||
received: categories.length,
|
received: categories.length,
|
||||||
normalized,
|
normalized: ordered,
|
||||||
anyEnabled: normalized.some((cat) => cat.enabled),
|
anyEnabled: ordered.some((cat) => cat.enabled),
|
||||||
toolExamples: normalized.slice(0, 3)
|
toolExamples: ordered.slice(0, 3)
|
||||||
});
|
});
|
||||||
this.toolSetSettings(normalized);
|
this.toolSetSettings(ordered);
|
||||||
this.toolSetSettingsLoading(false);
|
this.toolSetSettingsLoading(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user