feat: Implement API routes and dashboard UI for managing tools and MCP servers.

This commit is contained in:
advent259141
2026-03-10 22:22:18 +08:00
parent ff4412a627
commit 21f1fa82f4
3 changed files with 28 additions and 5 deletions

View File

@@ -324,9 +324,15 @@ class ToolsRoute(Route):
tools = self.tool_mgr.func_list
tools_dict = []
for tool in tools:
if isinstance(tool, MCPTool):
# Use the source field added to FunctionTool
source = getattr(tool, "source", "plugin")
if source == "mcp" and isinstance(tool, MCPTool):
origin = "mcp"
origin_name = tool.mcp_server_name
elif source == "internal":
origin = "internal"
origin_name = "AstrBot"
elif tool.handler_module_path and star_map.get(
tool.handler_module_path
):
@@ -344,6 +350,7 @@ class ToolsRoute(Route):
"active": tool.active,
"origin": origin,
"origin_name": origin_name,
"source": source,
}
tools_dict.append(tool_info)
return Response().ok(data=tools_dict).__dict__
@@ -361,6 +368,11 @@ class ToolsRoute(Route):
if not tool_name or action is None:
return Response().error("缺少必要参数: name 或 action").__dict__
# Internal tools cannot be toggled by users
for t in self.tool_mgr.func_list:
if t.name == tool_name and getattr(t, "source", "") == "internal":
return Response().error("内置工具不支持手动启用/停用").__dict__
if action:
try:
ok = self.tool_mgr.activate_llm_tool(tool_name, star_map=star_map)

View File

@@ -25,6 +25,7 @@ const toolHeaders = computed(() => [
]);
const parameterEntries = (tool: ToolItem) => Object.entries(tool.parameters?.properties || {});
const isInternal = (tool: ToolItem) => tool.source === 'internal';
</script>
<template>
@@ -39,9 +40,9 @@ const parameterEntries = (tool: ToolItem) => Object.entries(tool.parameters?.pro
:loading="props.loading"
>
<template #item.name="{ item }">
<div class="d-flex align-center py-2">
<v-icon color="primary" class="mr-2" size="18">
{{ item.name.includes(':') ? 'mdi-server-network' : 'mdi-function-variant' }}
<div class="d-flex align-center py-2" :class="{ 'internal-tool-row': isInternal(item) }">
<v-icon :color="isInternal(item) ? 'grey' : 'primary'" class="mr-2" size="18">
{{ isInternal(item) ? 'mdi-lock-outline' : (item.name.includes(':') ? 'mdi-server-network' : 'mdi-function-variant') }}
</v-icon>
<div>
<div class="text-subtitle-1 font-weight-medium">{{ item.name }}</div>
@@ -68,13 +69,17 @@ const parameterEntries = (tool: ToolItem) => Object.entries(tool.parameters?.pro
</template>
<template #item.active="{ item }">
<v-chip :color="item.active ? 'success' : 'error'" size="small" class="font-weight-medium" :variant="item.active ? 'flat' : 'outlined'">
<v-chip v-if="isInternal(item)" color="grey" size="small" class="font-weight-medium" variant="tonal">
内置
</v-chip>
<v-chip v-else :color="item.active ? 'success' : 'error'" size="small" class="font-weight-medium" :variant="item.active ? 'flat' : 'outlined'">
{{ item.active ? tmCommand('status.enabled') : tmCommand('status.disabled') }}
</v-chip>
</template>
<template #item.actions="{ item }">
<v-switch
v-if="!isInternal(item)"
:model-value="item.active"
color="primary"
density="compact"
@@ -82,6 +87,7 @@ const parameterEntries = (tool: ToolItem) => Object.entries(tool.parameters?.pro
inset
@update:model-value="emit('toggle-tool', item)"
/>
<span v-else class="text-caption text-grey"></span>
</template>
<template #no-data>
@@ -141,4 +147,8 @@ const parameterEntries = (tool: ToolItem) => Object.entries(tool.parameters?.pro
.tool-table :deep(.v-data-table__td) {
vertical-align: middle;
}
.internal-tool-row {
opacity: 0.65;
}
</style>

View File

@@ -99,5 +99,6 @@ export interface ToolItem {
};
origin?: string;
origin_name?: string;
source?: string;
}