chore: 移除 MCP 市场相关逻辑 (#2314)

This commit is contained in:
Soulter authored and GitHub committed 2025-08-04 17:40:21 +08:00
1 parent a8e7dadd39
commit d026a9f009
2 files changed
+64 -452

No files matched your search

-25
View File
@@ -267,31 +267,6 @@ class ToolsRoute(Route):
logger.error(traceback.format_exc())
return Response().error(f"删除 MCP 服务器失败: {str(e)}").__dict__
async def get_mcp_markets(self):
page = request.args.get("page", 1, type=int)
page_size = request.args.get("page_size", 10, type=int)
BASE_URL = (
"https://api.soulter.top/astrbot/mcpservers?page={}&page_size={}".format(
page,
page_size,
)
)
try:
async with aiohttp.ClientSession() as session:
async with session.get(f"{BASE_URL}") as response:
if response.status == 200:
data = await response.json()
return Response().ok(data["data"]).__dict__
else:
return (
Response()
.error(f"获取市场数据失败: HTTP {response.status}")
.__dict__
)
except Exception as _:
logger.error(traceback.format_exc())
return Response().error("获取市场数据失败").__dict__
async def test_mcp_connection(self):
"""
测试 MCP 服务器连接
+64 -427
View File
@@ -36,190 +36,81 @@
</div>
</v-row>
<!-- 标签页切换 -->
<v-tabs v-model="activeTab" color="primary" class="mb-6" show-arrows>
<v-tab value="local" class="font-weight-medium">
<v-icon start>mdi-server</v-icon>
{{ tm('tabs.local') }}
</v-tab>
<v-tab value="marketplace" class="font-weight-medium">
<v-icon start>mdi-store</v-icon>
{{ tm('tabs.marketplace') }}
<v-tooltip location="top" activator="parent">
<span>{{ tm('tooltip.marketplace') }}</span>
</v-tooltip>
</v-tab>
</v-tabs>
<!-- 本地服务器列表 -->
<v-window v-model="activeTab">
<!-- 本地服务器标签页内容 -->
<v-window-item value="local">
<!-- MCP 服务器部分 -->
<!-- MCP 服务器部分 -->
<div v-if="mcpServers.length === 0" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-server-off</v-icon>
<p class="text-grey mt-4">{{ tm('mcpServers.empty') }}</p>
</div>
<div v-if="mcpServers.length === 0" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-server-off</v-icon>
<p class="text-grey mt-4">{{ tm('mcpServers.empty') }}</p>
</div>
<v-row v-else>
<v-col v-for="(server, index) in mcpServers || []" :key="index" cols="12" md="6" lg="4" xl="3">
<item-card style="background-color: rgb(var(--v-theme-mcpCardBg));" :item="server" title-field="name"
enabled-field="active" @toggle-enabled="updateServerStatus" @delete="deleteServer" @edit="editServer">
<template v-slot:item-details="{ item }">
<div class="d-flex align-center mb-2">
<v-icon size="small" color="grey" class="me-2">mdi-file-code</v-icon>
<span class="text-caption text-medium-emphasis text-truncate" :title="getServerConfigSummary(item)">
{{ getServerConfigSummary(item) }}
</span>
</div>
<v-row v-else>
<v-col v-for="(server, index) in mcpServers || []" :key="index" cols="12" md="6" lg="4" xl="3">
<item-card style="background-color: rgb(var(--v-theme-mcpCardBg));" :item="server" title-field="name"
enabled-field="active" @toggle-enabled="updateServerStatus" @delete="deleteServer" @edit="editServer">
<template v-slot:item-details="{ item }">
<div class="d-flex align-center mb-2">
<v-icon size="small" color="grey" class="me-2">mdi-file-code</v-icon>
<span class="text-caption text-medium-emphasis text-truncate" :title="getServerConfigSummary(item)">
{{ getServerConfigSummary(item) }}
</span>
</div>
<div class="d-flex" style="gap: 8px;">
<div>
<div v-if="item.tools && item.tools.length > 0">
<div class="d-flex align-center mb-1">
<v-icon size="small" color="grey" class="me-2">mdi-tools</v-icon>
<v-dialog max-width="600px">
<template v-slot:activator="{ props: listToolsProps }">
<span class="text-caption text-medium-emphasis cursor-pointer" v-bind="listToolsProps"
style="text-decoration: underline;">
{{ tm('mcpServers.status.availableTools', { count: item.tools.length }) }} ({{
item.tools.length }})
</span>
</template>
<template v-slot:default="{ isActive }">
<v-card style="padding: 16px;">
<v-card-title class="d-flex align-center">
<span>{{ tm('mcpServers.status.availableTools') }}</span>
</v-card-title>
<v-card-text>
<ul>
<li v-for="(tool, idx) in item.tools" :key="idx" style="margin: 8px 0px;">{{
tool
}}
</li>
</ul>
</v-card-text>
<v-card-actions class="d-flex justify-end">
<v-btn variant="text" color="primary" @click="isActive.value = false">
Close
</v-btn>
</v-card-actions>
</v-card>
</template>
<div class="d-flex" style="gap: 8px;">
<div>
<div v-if="item.tools && item.tools.length > 0">
<div class="d-flex align-center mb-1">
<v-icon size="small" color="grey" class="me-2">mdi-tools</v-icon>
<v-dialog max-width="600px">
<template v-slot:activator="{ props: listToolsProps }">
<span class="text-caption text-medium-emphasis cursor-pointer" v-bind="listToolsProps"
style="text-decoration: underline;">
{{ tm('mcpServers.status.availableTools', { count: item.tools.length }) }} ({{
item.tools.length }})
</span>
</template>
<template v-slot:default="{ isActive }">
<v-card style="padding: 16px;">
<v-card-title class="d-flex align-center">
<span>{{ tm('mcpServers.status.availableTools') }}</span>
</v-card-title>
<v-card-text>
<ul>
<li v-for="(tool, idx) in item.tools" :key="idx" style="margin: 8px 0px;">{{
tool
}}
</li>
</ul>
</v-card-text>
<v-card-actions class="d-flex justify-end">
<v-btn variant="text" color="primary" @click="isActive.value = false">
Close
</v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
</div>
</div>
<div v-else class="text-caption text-medium-emphasis">
<v-icon size="small" color="warning" class="me-1">mdi-alert-circle</v-icon>
{{ tm('mcpServers.status.noTools') }}
</div>
</div>
<div v-if="mcpServerUpdateLoaders[item.name]" class="text-caption text-medium-emphasis">
<v-progress-circular indeterminate color="primary" size="16"></v-progress-circular>
</v-dialog>
</div>
</div>
</template>
</item-card>
</v-col>
</v-row>
</v-window-item>
<!-- MCP市场标签页内容 -->
<v-window-item value="marketplace">
<v-card elevation="2">
<v-card-title class="d-flex align-center py-3 px-4">
<v-icon color="primary" class="me-2">mdi-store</v-icon>
<span class="text-h6">{{ tm('marketplace.title') }}</span>
<v-spacer></v-spacer>
<v-text-field v-model="marketplaceSearch" prepend-inner-icon="mdi-magnify"
:label="tm('marketplace.search')" variant="outlined" density="compact" hide-details class="mx-2"
style="max-width: 300px" clearable @update:model-value="searchMarketplaceServers"></v-text-field>
<v-btn color="primary" prepend-icon="mdi-refresh" variant="text" @click="fetchMarketplaceServers(1)"
:loading="marketplaceLoading">
{{ tm('marketplace.buttons.refresh') }}
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-3">
<!-- 加载中 -->
<div v-if="marketplaceLoading" class="text-center pa-8">
<v-progress-circular indeterminate color="primary" size="64"></v-progress-circular>
<p class="text-grey mt-4">{{ tm('marketplace.loading') }}</p>
<div v-else class="text-caption text-medium-emphasis">
<v-icon size="small" color="warning" class="me-1">mdi-alert-circle</v-icon>
{{ tm('mcpServers.status.noTools') }}
</div>
</div>
<div v-if="mcpServerUpdateLoaders[item.name]" class="text-caption text-medium-emphasis">
<v-progress-circular indeterminate color="primary" size="16"></v-progress-circular>
</div>
</div>
<!-- 无数据 -->
<div v-else-if="filteredMarketplaceServers.length === 0" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-store-off</v-icon>
<p class="text-grey mt-4">{{ tm('marketplace.empty') }}</p>
</div>
<v-row v-else>
<v-col v-for="(server, index) in filteredMarketplaceServers" :key="index" cols="12" md="6" lg="4">
<v-card class="marketplace-card hover-elevation" height="100%">
<v-card-title class="d-flex align-center pb-1 pt-3">
<span class="text-h4 text-truncate" :title="server.name">
{{ server.name_h }}({{ server.name }})
</span>
<v-btn icon="mdi-open-in-new" variant="text" color="primary" class="ms-auto"
@click.stop="openurl(server.origin)"></v-btn>
</v-card-title>
<v-card-text>
<div class="d-flex align-center mb-2">
<v-icon size="small" color="grey" class="me-2">mdi-tools</v-icon>
<span class="text-caption text-medium-emphasis">
{{ tm('marketplace.status.availableTools', { count: server.tools ? server.tools.length : 0 })
}}
</span>
</div>
<v-chip-group class="tool-chips mb-2" v-if="server.tools && server.tools.length > 0">
<v-chip v-for="(tool, idx) in server.tools" :key="idx" size="x-small" density="compact"
color="info" class="text-caption">
{{ tool.name }}
</v-chip>
</v-chip-group>
<div v-else class="text-caption text-medium-emphasis mb-2">
<v-icon size="small" color="warning" class="me-1">mdi-alert-circle</v-icon>
{{ tm('marketplace.status.noToolsInfo') }}
</div>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-2">
<v-spacer></v-spacer>
<v-btn variant="text" size="small" color="info" prepend-icon="mdi-information-outline"
@click="showServerDetail(server)">
{{ tm('marketplace.buttons.detail') }}
</v-btn>
<v-btn variant="text" size="small" color="primary" prepend-icon="mdi-plus"
@click="importServerConfig(server)">
{{ tm('marketplace.buttons.import') }}
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<!-- 分页控件 -->
<div class="d-flex justify-center mt-4">
<v-pagination v-if="!marketplaceLoading && totalMarketPages > 1" v-model="currentMarketPage"
:length="totalMarketPages" total-visible="7" rounded @update:model-value="changePage"></v-pagination>
</div>
</v-card-text>
</v-card>
</v-window-item>
</v-window>
</template>
</item-card>
</v-col>
</v-row>
</v-container>
<!-- 添加/编辑 MCP 服务器对话框 -->
@@ -347,106 +238,6 @@
</v-card>
</v-dialog>
<!-- 服务器详情对话框 -->
<v-dialog v-model="showServerDetailDialog" max-width="800px">
<v-card>
<v-card-title class="bg-primary text-white py-3">
<v-icon color="white" class="me-2">mdi-information-outline</v-icon>
<span>{{ tm('dialogs.serverDetail.title') }}</span>
<v-spacer></v-spacer>
<v-btn icon variant="text" color="white" @click="showServerDetailDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text v-if="selectedMarketplaceServer" class="py-4">
<h2 class="text-h5 mb-3">{{ selectedMarketplaceServer.name }}</h2>
<div class="mb-4">
<h3 class="text-subtitle-1 font-weight-bold mb-2">{{ tm('dialogs.serverDetail.installConfig') }}</h3>
<div class="monaco-container" style="height: 200px">
<VueMonacoEditor v-model:value="selectedServerConfigDisplay" theme="vs-dark" language="json" :options="{
readOnly: true,
minimap: {
enabled: false
},
scrollBeyondLastLine: false,
automaticLayout: true,
lineNumbers: 'on',
tabSize: 2
}" />
</div>
</div>
<div v-if="selectedMarketplaceServer.tools && selectedMarketplaceServer.tools.length > 0">
<h3 class="text-subtitle-1 font-weight-bold mb-2">
{{ tm('dialogs.serverDetail.availableTools') }}
<v-chip color="info" size="small" class="ml-1">{{ selectedMarketplaceServer.tools.length }}</v-chip>
</h3>
<v-expansion-panels>
<v-expansion-panel v-for="(tool, index) in selectedMarketplaceServer.tools" :key="index" class="mb-2">
<v-expansion-panel-title>
<div class="d-flex align-center">
<v-icon color="primary" class="me-2" size="small">mdi-function-variant</v-icon>
<span class="font-weight-medium">{{ tool.name }}</span>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<p class="mb-3">{{ tool.description }}</p>
<template v-if="tool.inputSchema && tool.inputSchema.properties">
<h4 class="text-subtitle-2 mb-2">{{ tm('functionTools.parameters') }}</h4>
<v-table density="compact">
<thead>
<tr>
<th>{{ tm('functionTools.table.paramName') }}</th>
<th>{{ tm('functionTools.table.type') }}</th>
<th>{{ tm('functionTools.table.required') }}</th>
<th>{{ tm('functionTools.table.description') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(param, paramName) in tool.inputSchema.properties" :key="paramName">
<td class="font-weight-medium">{{ paramName }}</td>
<td>
<v-chip size="x-small" color="primary" text>
{{ param.type }}
</v-chip>
</td>
<td>
<v-icon v-if="tool.inputSchema.required && tool.inputSchema.required.includes(paramName)"
color="error" size="small">
mdi-check
</v-icon>
<span v-else>{{ t('core.common.no') }}</span>
</td>
<td>{{ param.description }}</td>
</tr>
</tbody>
</v-table>
</template>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn variant="text" @click="showServerDetailDialog = false">
{{ tm('dialogs.serverDetail.buttons.close') }}
</v-btn>
<v-btn color="primary" prepend-icon="mdi-plus" @click="importServerConfig(selectedMarketplaceServer)">
{{ tm('dialogs.serverDetail.buttons.importConfig') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 函数工具对话框 -->
<v-dialog v-model="showToolsDialog" max-width="800px">
<v-card elevation="0" class="mt-4">
@@ -583,7 +374,6 @@ export default {
data() {
return {
refreshInterval: null,
activeTab: 'local', // 当前激活的标签页
mcpServers: [],
tools: [],
showMcpServerDialog: false,
@@ -593,7 +383,6 @@ export default {
mcpProviderToken: '',
showSyncMcpServerDialog: false,
showServerDetailDialog: false,
addServerDialogMessage: "",
showToolsDialog: false,
showTools: true,
@@ -613,19 +402,6 @@ export default {
save_message_success: "success",
toolSearch: '',
openedPanel: [], // 存储打开的面板索引
// MCP 市场相关
marketplaceServers: [],
marketplaceLoading: false,
marketplaceSearch: '',
selectedMarketplaceServer: null,
selectedServerConfigDisplay: '',
// 分页相关
currentMarketPage: 1,
marketPageSize: 9, // 每页显示9个服务器,适合3列布局
totalMarketPages: 1,
totalMarketItems: 0,
}
},
@@ -663,26 +439,11 @@ export default {
return this.tm('mcpServers.status.noConfig');
}
},
// 过滤后的市场服务器
filteredMarketplaceServers() {
if (!this.marketplaceSearch.trim()) {
return this.marketplaceServers;
}
const searchTerm = this.marketplaceSearch.toLowerCase();
return this.marketplaceServers.filter(server =>
server.name.toLowerCase().includes(searchTerm) ||
(server.name_h && server.name_h.toLowerCase().includes(searchTerm)) ||
(server.description && server.description.toLowerCase().includes(searchTerm))
);
},
},
mounted() {
this.getServers();
this.getTools();
this.fetchMarketplaceServers();
this.refreshInterval = setInterval(() => {
this.getServers();
@@ -940,116 +701,7 @@ export default {
this.save_message_snack = true;
},
// MCP 市场相关方法
// 获取市场服务器列表
fetchMarketplaceServers(page = 1) {
this.marketplaceLoading = true;
// 构建请求参数
const params = {
page: page,
page_size: this.marketPageSize
};
// 如果有搜索关键词,添加到请求参数
if (this.marketplaceSearch.trim()) {
params.search = this.marketplaceSearch.trim();
}
axios.get('/api/tools/mcp/market', { params })
.then(response => {
this.marketplaceServers = response.data.data.mcpservers || [];
// 更新分页信息
if (response.data.data.pagination) {
this.totalMarketItems = response.data.data.pagination.total || 0;
this.totalMarketPages = response.data.data.pagination.totalPages || 1;
this.currentMarketPage = response.data.data.pagination.currentPage || 1;
} else {
// 如果后端没有返回分页信息,根据返回的数据量估算
this.totalMarketPages = Math.ceil(this.marketplaceServers.length / this.marketPageSize) || 1;
}
this.marketplaceLoading = false;
})
.catch(error => {
this.showError(this.tm('messages.getMarketError', { error: error.message }));
this.marketplaceLoading = false;
});
},
// 搜索市场服务器
searchMarketplaceServers() {
// 重置到第一页,然后获取结果
this.currentMarketPage = 1;
this.fetchMarketplaceServers(1);
},
// 切换分页
changePage(page) {
this.fetchMarketplaceServers(page);
},
// 显示服务器详情
showServerDetail(server) {
this.selectedMarketplaceServer = server;
// 格式化服务器配置的显示
try {
if (server.config) {
const configs = JSON.parse(server.config);
this.selectedServerConfigDisplay = JSON.stringify(configs[0], null, 2);
} else {
this.selectedServerConfigDisplay = '// ' + this.tm('messages.noAvailableConfig');
}
} catch (e) {
this.selectedServerConfigDisplay = '// ' + this.tm('messages.configParseError', { error: e.message });
}
this.showServerDetailDialog = true;
},
// 导入服务器配置
importServerConfig(server) {
try {
// 解析服务器配置
if (!server.config) {
this.showError(this.tm('messages.importError.noConfig'));
return;
}
const configs = JSON.parse(server.config);
if (!configs || !configs[0] || !configs[0].mcpServers) {
this.showError(this.tm('messages.importError.invalidFormat'));
return;
}
// 找到服务器名称和配置
const serverName = server.name;
const serverConfig = configs[0]
// 设置表单数据
this.currentServer = {
name: serverName,
active: true,
tools: []
};
// 设置配置JSON
this.serverConfigJson = JSON.stringify(serverConfig, null, 2);
// 关闭详情对话框(如果打开的话)
this.showServerDetailDialog = false;
// 打开添加服务器对话框
this.isEditMode = false;
this.showMcpServerDialog = true;
} catch (e) {
this.showError(this.tm('messages.importError.failed', { error: e.message }));
}
},
// MCP 市场相关方法已移除
// 切换工具状态
async toggleToolStatus(tool) {
@@ -1158,19 +810,4 @@ export default {
margin-top: 4px;
overflow: hidden;
}
.marketplace-card {
position: relative;
border-radius: 8px;
transition: all 0.3s ease;
}
.text-truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>