feat: mcp 服务器市场

This commit is contained in:
Soulter
2025-04-17 00:41:04 +08:00
parent aba18232b1
commit 97cbccc2ba
4 changed files with 540 additions and 185 deletions
+25 -2
View File
@@ -1,5 +1,6 @@
import os
import json
import aiohttp
import traceback
from .route import Route, Response, RouteContext
from quart import request
@@ -20,6 +21,7 @@ class ToolsRoute(Route):
"/tools/mcp/add": ("POST", self.add_mcp_server),
"/tools/mcp/update": ("POST", self.update_mcp_server),
"/tools/mcp/delete": ("POST", self.delete_mcp_server),
"/tools/mcp/market": ("GET", self.get_mcp_markets),
}
self.register_routes()
self.tool_mgr = self.core_lifecycle.provider_manager.llm_tools
@@ -107,7 +109,9 @@ class ToolsRoute(Route):
for key, value in server_data.items():
if key not in ["name", "active", "tools"]: # 排除特殊字段
if key == "mcpServers":
key_0 = list(server_data["mcpServers"].keys())[0] # 不考虑为空的情况
key_0 = list(server_data["mcpServers"].keys())[
0
] # 不考虑为空的情况
server_config = server_data["mcpServers"][key_0]
else:
server_config[key] = value
@@ -168,7 +172,9 @@ class ToolsRoute(Route):
for key, value in server_data.items():
if key not in ["name", "active", "tools"]: # 排除特殊字段
if key == "mcpServers":
key_0 = list(server_data["mcpServers"].keys())[0] # 不考虑为空的情况
key_0 = list(server_data["mcpServers"].keys())[
0
] # 不考虑为空的情况
server_config = server_data["mcpServers"][key_0]
else:
server_config[key] = value
@@ -258,3 +264,20 @@ class ToolsRoute(Route):
except Exception as e:
logger.error(traceback.format_exc())
return Response().error(f"删除 MCP 服务器失败: {str(e)}").__dict__
async def get_mcp_markets(self):
BASE_URL = "https://api.soulter.top/astrbot/mcpservers"
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())
@@ -81,7 +81,7 @@ const viewReadme = () => {
<div class="flex-grow-1">
<div>{{ extension.author }} /</div>
<p class="text-h3 font-weight-black" :class="{ 'text-h4': $vuetify.display.xs }">
<p class="text-h4 font-weight-black" :class="{ 'text-h4': $vuetify.display.xs }">
{{ extension.name }}
<v-tooltip location="top" v-if="extension?.has_update && !marketMode">
<template v-slot:activator="{ props: tooltipProps }">
+1 -1
View File
@@ -402,7 +402,7 @@ onMounted(async () => {
已安装的插件
</v-card-title>
<v-card-subtitle class="text-subtitle-1 mt-1 text-medium-emphasis">
管理已经安装的所有机器人插件
管理已经安装的所有插件
</v-card-subtitle>
</v-card-item>
+513 -181
View File
@@ -27,198 +27,315 @@
</v-col>
</v-row>
<!-- MCP 服务器部分 -->
<v-card class="mb-6" elevation="2">
<v-card-title class="d-flex align-center py-3 px-4">
<v-icon color="primary" class="me-2">mdi-server</v-icon>
<span class="text-h6">MCP 服务器</span>
<v-progress-circular indeterminate color="primary" size="24" style="margin-left: 16px;" v-show="loading"></v-progress-circular>
<v-spacer></v-spacer>
<v-btn color="primary" prepend-icon="mdi-plus" variant="tonal" @click="showMcpServerDialog = true">
新增服务器
</v-btn>
</v-card-title>
<!-- 标签页切换 -->
<v-tabs
v-model="activeTab"
color="primary"
class="mb-4"
show-arrows
>
<v-tab value="local" class="font-weight-medium">
<v-icon start>mdi-server</v-icon>
本地服务器
</v-tab>
<v-tab value="marketplace" class="font-weight-medium">
<v-icon start>mdi-store</v-icon>
MCP 市场
<v-tooltip location="top" activator="parent">
<span>浏览和安装来自社区的 MCP 服务器</span>
</v-tooltip>
</v-tab>
</v-tabs>
<v-divider></v-divider>
<v-window v-model="activeTab">
<!-- 本地服务器标签页内容 -->
<v-window-item value="local">
<!-- MCP 服务器部分 -->
<v-card class="mb-6" elevation="2">
<v-card-title class="d-flex align-center py-3 px-4">
<v-icon color="primary" class="me-2">mdi-server</v-icon>
<span class="text-h6">MCP 服务器</span>
<v-progress-circular indeterminate color="primary" size="24" style="margin-left: 16px;" v-show="loading"></v-progress-circular>
<v-spacer></v-spacer>
<v-btn color="primary" prepend-icon="mdi-plus" variant="tonal" @click="showMcpServerDialog = true">
新增服务器
</v-btn>
</v-card-title>
<v-card-text class="px-4 py-3">
<v-row v-if="mcpServers.length === 0">
<v-col cols="12" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-server-off</v-icon>
<p class="text-grey mt-4">暂无 MCP 服务器点击"新增服务器"添加</p>
</v-col>
</v-row>
<v-divider></v-divider>
<v-row v-else>
<v-col v-for="(server, index) in mcpServers" :key="index" cols="12" md="6" lg="4" xl="3">
<v-card class="server-card hover-elevation" :color="server.active ? '' : 'grey-lighten-4'">
<div class="server-status-indicator" :class="{'active': server.active}"></div>
<v-card-title class="d-flex justify-space-between align-center pb-1 pt-3">
<span class="text-h6 text-truncate" :title="server.name">{{ server.name }}</span>
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-switch color="primary" hide-details density="compact" v-model="server.active"
v-bind="props" @update:modelValue="updateServerStatus(server)"></v-switch>
</template>
<span>{{ server.active ? '已启用' : '已禁用' }}</span>
</v-tooltip>
</v-card-title>
<v-card-text class="px-4 py-3">
<v-row v-if="mcpServers.length === 0">
<v-col cols="12" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-server-off</v-icon>
<p class="text-grey mt-4">暂无 MCP 服务器点击"新增服务器"添加</p>
</v-col>
</v-row>
<v-row v-else>
<v-col v-for="(server, index) in mcpServers" :key="index" cols="12" md="6" lg="4" xl="3">
<v-card class="server-card hover-elevation" :color="server.active ? '' : 'grey-lighten-4'">
<div class="server-status-indicator" :class="{'active': server.active}"></div>
<v-card-title class="d-flex justify-space-between align-center pb-1 pt-3">
<span class="text-h4 text-truncate" :title="server.name">{{ server.name }}</span>
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-switch color="primary" hide-details density="compact" v-model="server.active"
v-bind="props" @update:modelValue="updateServerStatus(server)"></v-switch>
</template>
<span>{{ server.active ? '已启用' : '已禁用' }}</span>
</v-tooltip>
</v-card-title>
<v-card-text>
<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(server)">
{{ getServerConfigSummary(server) }}
</span>
</div>
<div v-if="server.tools && server.tools.length > 0">
<div class="d-flex align-center mb-1">
<v-icon size="small" color="grey" class="me-2">mdi-tools</v-icon>
<span class="text-caption text-medium-emphasis">可用工具 ({{ server.tools.length }})</span>
</div>
<v-chip-group class="tool-chips">
<v-chip v-for="(tool, idx) in server.tools" :key="idx" size="x-small"
density="compact" color="info" class="text-caption">
{{ tool }}
</v-chip>
</v-chip-group>
</div>
<div v-else class="text-caption text-medium-emphasis mt-2">
<v-icon size="small" color="warning" class="me-1">mdi-alert-circle</v-icon>
无可用工具
</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="error" prepend-icon="mdi-delete"
@click="deleteServer(server.name)">
删除
</v-btn>
<v-btn variant="text" size="small" color="primary" prepend-icon="mdi-pencil"
@click="editServer(server)">
编辑
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- 函数工具部分 -->
<v-card elevation="2">
<v-card-title class="d-flex align-center py-3 px-4">
<v-icon color="primary" class="me-2">mdi-function</v-icon>
<span class="text-h6">函数工具</span>
<v-chip color="info" size="small" class="ml-2">{{ tools.length }}</v-chip>
<v-spacer></v-spacer>
<v-btn variant="text" color="primary" @click="showTools = !showTools">
{{ showTools ? '收起' : '展开' }}
<v-icon>{{ showTools ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-expand-transition>
<v-card-text class="pa-3" v-if="showTools">
<div v-if="tools.length === 0" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-api-off</v-icon>
<p class="text-grey mt-4">没有可用的函数工具</p>
</div>
<v-card-text>
<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(server)">
{{ getServerConfigSummary(server) }}
</span>
</div>
<div v-if="server.tools && server.tools.length > 0">
<div class="d-flex align-center mb-1">
<v-icon size="small" color="grey" class="me-2">mdi-tools</v-icon>
<span class="text-caption text-medium-emphasis">可用工具 ({{ server.tools.length }})</span>
</div>
<v-chip-group class="tool-chips">
<v-chip v-for="(tool, idx) in server.tools" :key="idx" size="x-small"
density="compact" color="info" class="text-caption">
{{ tool }}
</v-chip>
</v-chip-group>
</div>
<div v-else class="text-caption text-medium-emphasis mt-2">
<v-icon size="small" color="warning" class="me-1">mdi-alert-circle</v-icon>
无可用工具
</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="error" prepend-icon="mdi-delete"
@click="deleteServer(server.name)">
删除
</v-btn>
<v-btn variant="text" size="small" color="primary" prepend-icon="mdi-pencil"
@click="editServer(server)">
编辑
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
<div v-else>
<v-text-field
v-model="toolSearch"
prepend-inner-icon="mdi-magnify"
label="搜索函数工具"
variant="outlined"
density="compact"
class="mb-4"
hide-details
clearable
></v-text-field>
<!-- 函数工具部分 -->
<v-card elevation="2">
<v-card-title class="d-flex align-center py-3 px-4">
<v-icon color="primary" class="me-2">mdi-function</v-icon>
<span class="text-h6">函数工具</span>
<v-chip color="info" size="small" class="ml-2">{{ tools.length }}</v-chip>
<v-spacer></v-spacer>
<v-btn variant="text" color="primary" @click="showTools = !showTools">
{{ showTools ? '收起' : '展开' }}
<v-icon>{{ showTools ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn>
</v-card-title>
<v-expansion-panels v-model="openedPanel" multiple>
<v-expansion-panel
v-for="(tool, index) in filteredTools"
:key="index"
:value="index"
class="mb-2 tool-panel"
rounded="lg"
>
<v-expansion-panel-title>
<v-row no-gutters align="center">
<v-col cols="3">
<div class="d-flex align-center">
<v-icon color="primary" class="me-2" size="small">
{{ tool.function.name.includes(':') ? 'mdi-server-network' : 'mdi-function-variant' }}
</v-icon>
<span class="text-body-1 text-high-emphasis font-weight-medium text-truncate"
:title="tool.function.name">
{{ formatToolName(tool.function.name) }}
</span>
</div>
</v-col>
<v-col cols="9" class="text-grey">
{{ tool.function.description }}
</v-col>
</v-row>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-card flat>
<v-card-text>
<p class="text-body-1 font-weight-medium mb-3">
<v-icon color="primary" size="small" class="me-1">mdi-information</v-icon>
功能描述
</p>
<p class="text-body-2 ml-6 mb-4">{{ tool.function.description }}</p>
<template v-if="tool.function.parameters && tool.function.parameters.properties">
<p class="text-body-1 font-weight-medium mb-3">
<v-icon color="primary" size="small" class="me-1">mdi-code-json</v-icon>
参数列表
</p>
<v-table density="compact" class="params-table mt-1">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr v-for="(param, paramName) in tool.function.parameters.properties" :key="paramName">
<td class="font-weight-medium">{{ paramName }}</td>
<td>
<v-chip size="x-small" color="primary" text class="text-caption">
{{ param.type }}
</v-chip>
</td>
<td>{{ param.description }}</td>
</tr>
</tbody>
</v-table>
</template>
<div v-else class="text-center pa-4 text-medium-emphasis">
<v-icon size="large" color="grey-lighten-1">mdi-code-brackets</v-icon>
<p>此工具没有参数</p>
</div>
</v-card-text>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
</v-card-text>
</v-expand-transition>
</v-card>
</v-window-item>
<v-divider></v-divider>
<v-expand-transition>
<v-card-text class="pa-3" v-if="showTools">
<div v-if="tools.length === 0" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-api-off</v-icon>
<p class="text-grey mt-4">没有可用的函数工具</p>
</div>
<div v-else>
<!-- 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">MCP 服务器市场</span>
<v-spacer></v-spacer>
<v-text-field
v-model="toolSearch"
v-model="marketplaceSearch"
prepend-inner-icon="mdi-magnify"
label="搜索函数工具"
label="搜索服务器"
variant="outlined"
density="compact"
class="mb-4"
hide-details
class="mx-2"
style="max-width: 300px"
clearable
></v-text-field>
<v-btn color="primary" prepend-icon="mdi-refresh" variant="text" @click="fetchMarketplaceServers" :loading="marketplaceLoading">
刷新
</v-btn>
</v-card-title>
<v-expansion-panels v-model="openedPanel" multiple>
<v-expansion-panel
v-for="(tool, index) in filteredTools"
:key="index"
:value="index"
class="mb-2 tool-panel"
rounded="lg"
>
<v-expansion-panel-title>
<v-row no-gutters align="center">
<v-col cols="3">
<div class="d-flex align-center">
<v-icon color="primary" class="me-2" size="small">
{{ tool.function.name.includes(':') ? 'mdi-server-network' : 'mdi-function-variant' }}
</v-icon>
<span class="text-body-1 text-high-emphasis font-weight-medium text-truncate"
:title="tool.function.name">
{{ formatToolName(tool.function.name) }}
</span>
</div>
</v-col>
<v-col cols="9" class="text-grey">
{{ tool.function.description }}
</v-col>
</v-row>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-card flat>
<v-card-text>
<p class="text-body-1 font-weight-medium mb-3">
<v-icon color="primary" size="small" class="me-1">mdi-information</v-icon>
功能描述
</p>
<p class="text-body-2 ml-6 mb-4">{{ tool.function.description }}</p>
<template v-if="tool.function.parameters && tool.function.parameters.properties">
<p class="text-body-1 font-weight-medium mb-3">
<v-icon color="primary" size="small" class="me-1">mdi-code-json</v-icon>
参数列表
</p>
<v-table density="compact" class="params-table mt-1">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr v-for="(param, paramName) in tool.function.parameters.properties" :key="paramName">
<td class="font-weight-medium">{{ paramName }}</td>
<td>
<v-chip size="x-small" color="primary" text class="text-caption">
{{ param.type }}
</v-chip>
</td>
<td>{{ param.description }}</td>
</tr>
</tbody>
</v-table>
</template>
<div v-else class="text-center pa-4 text-medium-emphasis">
<v-icon size="large" color="grey-lighten-1">mdi-code-brackets</v-icon>
<p>此工具没有参数</p>
</div>
</v-card-text>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
</v-card-text>
</v-expand-transition>
</v-card>
<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">正在加载 MCP 服务器市场...</p>
</div>
<!-- 无数据 -->
<div v-else-if="marketplaceServers.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">暂无可用的 MCP 服务器</p>
</div>
<!-- 服务器列表 -->
<v-row v-else>
<v-col v-for="(server, index) in marketplaceServers" :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-h6 text-truncate" :title="server.name">
{{ server.name_h }}({{ server.name }})
</span>
</v-card-title>
<v-card-text>
<!-- <p class="text-body-2 mb-3 text-truncate-2" :title="server.AbstractCN">
{{ server.AbstractCN || server.Abstract || '暂无描述' }}
</p> -->
<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">
可用工具 ({{ 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>
无可用工具信息
</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)">
详情
</v-btn>
<v-btn variant="text" size="small" color="primary" prepend-icon="mdi-plus"
@click="importServerConfig(server)">
导入
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-window-item>
</v-window>
</v-container>
<!-- 添加/编辑 MCP 服务器对话框 -->
@@ -324,6 +441,119 @@
</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>服务器详情</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>
<!-- <v-alert color="info" variant="tonal" class="mb-3">
{{ selectedMarketplaceServer.AbstractCN || selectedMarketplaceServer.Abstract || '暂无描述' }}
</v-alert> -->
<div class="mb-4">
<h3 class="text-subtitle-1 font-weight-bold mb-2">安装配置</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">
可用工具
<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">参数列表</h4>
<v-table density="compact">
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>必填</th>
<th>描述</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></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">
关闭
</v-btn>
<v-btn color="primary" prepend-icon="mdi-plus" @click="importServerConfig(selectedMarketplaceServer)">
导入配置
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 消息提示 -->
<v-snackbar :timeout="3000" elevation="24" :color="save_message_success" v-model="save_message_snack"
location="top">
@@ -345,9 +575,11 @@ export default {
},
data() {
return {
activeTab: 'local', // 当前激活的标签页
mcpServers: [],
tools: [],
showMcpServerDialog: false,
showServerDetailDialog: false,
showTools: true,
loading: false,
isEditMode: false,
@@ -363,6 +595,13 @@ export default {
save_message_success: "success",
toolSearch: '',
openedPanel: [], // 存储打开的面板索引
// MCP 市场相关
marketplaceServers: [],
marketplaceLoading: false,
marketplaceSearch: '',
selectedMarketplaceServer: null,
selectedServerConfigDisplay: '',
}
},
@@ -399,23 +638,26 @@ export default {
return '未设置配置';
}
}
},
},
mounted() {
this.getServers();
this.getTools();
this.fetchMarketplaceServers();
// 定期刷新本地服务器列表
setInterval(() => {
this.getServers();
this.getTools();
}, 5000); // 每 5 秒刷新一次服务器列表
}, 5000);
},
methods: {
openurl(url) {
window.open(url, '_blank');
},
formatToolName(name) {
if (name.includes(':')) {
// MCP 工具通常命名为 mcp:server:tool
@@ -590,6 +832,82 @@ export default {
this.save_message = message;
this.save_message_success = "error";
this.save_message_snack = true;
},
// MCP 市场相关方法
// 获取市场服务器列表
fetchMarketplaceServers() {
this.marketplaceLoading = true;
axios.get('/api/tools/mcp/market')
.then(response => {
this.marketplaceServers = response.data.data.mcpservers || [];
this.marketplaceLoading = false;
})
.catch(error => {
this.showError("获取 MCP 市场服务器列表失败: " + error.message);
this.marketplaceLoading = false;
});
},
// 显示服务器详情
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 = '// 无可用配置';
}
} catch (e) {
this.selectedServerConfigDisplay = '// 配置解析错误: ' + e.message;
}
this.showServerDetailDialog = true;
},
// 导入服务器配置
importServerConfig(server) {
try {
// 解析服务器配置
if (!server.config) {
this.showError('此服务器没有可用配置');
return;
}
const configs = JSON.parse(server.config);
if (!configs || !configs[0] || !configs[0].mcpServers) {
this.showError('服务器配置格式不正确');
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('导入配置失败: ' + e.message);
}
}
}
}
@@ -655,4 +973,18 @@ export default {
height: 300px;
overflow: hidden;
}
.marketplace-card {
position: relative;
border-radius: 8px;
transition: all 0.3s ease;
}
.text-truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>