Merge pull request #1296 from AstrBotDevs/feat-mcp-servers-market
[WIP] MCP 服务器市场
This commit is contained in:
+4
-1
@@ -25,6 +25,7 @@ import logging
|
||||
import colorlog
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from collections import deque
|
||||
from asyncio import Queue
|
||||
from typing import List
|
||||
@@ -171,7 +172,9 @@ class LogManager:
|
||||
if logger.hasHandlers():
|
||||
return logger
|
||||
# 如果logger没有处理器
|
||||
console_handler = logging.StreamHandler() # 创建一个StreamHandler用于控制台输出
|
||||
console_handler = logging.StreamHandler(
|
||||
sys.stdout
|
||||
) # 创建一个StreamHandler用于控制台输出
|
||||
console_handler.setLevel(
|
||||
logging.DEBUG
|
||||
) # 将日志级别设置为DEBUG(最低级别, 显示所有日志), *如果插件没有设置级别, 默认为DEBUG
|
||||
|
||||
@@ -4,12 +4,14 @@ import textwrap
|
||||
import os
|
||||
import asyncio
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from typing import Dict, List, Awaitable, Literal, Any
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from contextlib import AsyncExitStack
|
||||
from astrbot import logger
|
||||
from astrbot.core.utils.log_pipe import LogPipe
|
||||
|
||||
try:
|
||||
import mcp
|
||||
@@ -87,8 +89,9 @@ class MCPClient:
|
||||
self.name = None
|
||||
self.active: bool = True
|
||||
self.tools: List[mcp.Tool] = []
|
||||
self.server_errlogs: List[str] = []
|
||||
|
||||
async def connect_to_server(self, mcp_server_config: dict):
|
||||
async def connect_to_server(self, mcp_server_config: dict, name: str):
|
||||
"""Connect to an MCP server
|
||||
|
||||
Args:
|
||||
@@ -98,19 +101,30 @@ class MCPClient:
|
||||
if "mcpServers" in cfg and len(cfg["mcpServers"]) > 0:
|
||||
key_0 = list(cfg["mcpServers"].keys())[0]
|
||||
cfg = cfg["mcpServers"][key_0]
|
||||
cfg.pop("active", None) # Remove active flag from config
|
||||
cfg.pop("active", None) # Remove active flag from config
|
||||
server_params = mcp.StdioServerParameters(
|
||||
**cfg,
|
||||
)
|
||||
|
||||
def callback(msg: str):
|
||||
# 处理 MCP 服务的错误日志
|
||||
self.server_errlogs.append(msg)
|
||||
|
||||
stdio_transport = await self.exit_stack.enter_async_context(
|
||||
mcp.stdio_client(server_params)
|
||||
mcp.stdio_client(
|
||||
server_params,
|
||||
errlog=LogPipe(
|
||||
level=logging.ERROR,
|
||||
logger=logger,
|
||||
identifier=f"MCPServer-{name}",
|
||||
callback=callback,
|
||||
),
|
||||
),
|
||||
)
|
||||
self.stdio, self.write = stdio_transport
|
||||
self.session = await self.exit_stack.enter_async_context(
|
||||
mcp.ClientSession(self.stdio, self.write)
|
||||
)
|
||||
|
||||
await self.session.initialize()
|
||||
|
||||
async def list_tools_and_save(self) -> mcp.ListToolsResult:
|
||||
@@ -266,7 +280,9 @@ class FuncCall:
|
||||
self.func_list = [
|
||||
f
|
||||
for f in self.func_list
|
||||
if not (f.origin == "mcp" and f.mcp_server_name == data["name"])
|
||||
if not (
|
||||
f.origin == "mcp" and f.mcp_server_name == data["name"]
|
||||
)
|
||||
]
|
||||
else:
|
||||
for name in self.mcp_client_dict.keys():
|
||||
@@ -275,11 +291,7 @@ class FuncCall:
|
||||
if name in self.mcp_client_event:
|
||||
self.mcp_client_event[name].set()
|
||||
self.mcp_client_event.pop(name, None)
|
||||
self.func_list = [
|
||||
f
|
||||
for f in self.func_list
|
||||
if f.origin != "mcp"
|
||||
]
|
||||
self.func_list = [f for f in self.func_list if f.origin != "mcp"]
|
||||
|
||||
async def _init_mcp_client_task_wrapper(
|
||||
self, name: str, cfg: dict, event: asyncio.Event
|
||||
@@ -291,6 +303,9 @@ class FuncCall:
|
||||
logger.info(f"收到 MCP 客户端 {name} 终止信号")
|
||||
await self._terminate_mcp_client(name)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
logger.error(f"初始化 MCP 客户端 {name} 失败: {e}")
|
||||
|
||||
async def _init_mcp_client(self, name: str, config: dict) -> None:
|
||||
@@ -302,10 +317,10 @@ class FuncCall:
|
||||
|
||||
mcp_client = MCPClient()
|
||||
mcp_client.name = name
|
||||
await mcp_client.connect_to_server(config)
|
||||
self.mcp_client_dict[name] = mcp_client
|
||||
await mcp_client.connect_to_server(config, name)
|
||||
tools_res = await mcp_client.list_tools_and_save()
|
||||
tool_names = [tool.name for tool in tools_res.tools]
|
||||
self.mcp_client_dict[name] = mcp_client
|
||||
|
||||
# 移除该MCP服务之前的工具(如有)
|
||||
self.func_list = [
|
||||
@@ -329,6 +344,9 @@ class FuncCall:
|
||||
logger.info(f"已连接 MCP 服务 {name}, Tools: {tool_names}")
|
||||
return True
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error(f"初始化 MCP 客户端 {name} 失败: {e}")
|
||||
# 发生错误时确保客户端被清理
|
||||
if name in self.mcp_client_dict:
|
||||
@@ -352,7 +370,7 @@ class FuncCall:
|
||||
]
|
||||
logger.info(f"已关闭 MCP 服务 {name}")
|
||||
|
||||
def get_func_desc_openai_style(self, omit_empty_parameter_field = False) -> list:
|
||||
def get_func_desc_openai_style(self, omit_empty_parameter_field=False) -> list:
|
||||
"""
|
||||
获得 OpenAI API 风格的**已经激活**的工具描述
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import threading
|
||||
import os
|
||||
from logging import Logger
|
||||
|
||||
|
||||
class LogPipe(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
level,
|
||||
logger: Logger,
|
||||
identifier=None,
|
||||
callback=None,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.level = level
|
||||
self.fd_read, self.fd_write = os.pipe()
|
||||
self.identifier = identifier
|
||||
self.logger = logger
|
||||
self.callback = callback
|
||||
self.reader = os.fdopen(self.fd_read)
|
||||
self.start()
|
||||
|
||||
def fileno(self):
|
||||
return self.fd_write
|
||||
|
||||
def run(self):
|
||||
for line in iter(self.reader.readline, ""):
|
||||
if self.callback:
|
||||
self.callback(line.strip())
|
||||
self.logger.log(self.level, f"[{self.identifier}] {line.strip()}")
|
||||
|
||||
self.reader.close()
|
||||
|
||||
def close(self):
|
||||
os.close(self.fd_write)
|
||||
@@ -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
|
||||
@@ -78,6 +80,7 @@ class ToolsRoute(Route):
|
||||
) in self.tool_mgr.mcp_client_dict.items():
|
||||
if name_key == name:
|
||||
server_info["tools"] = [tool.name for tool in mcp_client.tools]
|
||||
server_info["errlogs"] = mcp_client.server_errlogs
|
||||
break
|
||||
else:
|
||||
server_info["tools"] = []
|
||||
@@ -105,9 +108,11 @@ class ToolsRoute(Route):
|
||||
|
||||
# 复制所有配置字段
|
||||
for key, value in server_data.items():
|
||||
if key not in ["name", "active", "tools"]: # 排除特殊字段
|
||||
if key not in ["name", "active", "tools", "errlogs"]: # 排除特殊字段
|
||||
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
|
||||
@@ -125,7 +130,7 @@ class ToolsRoute(Route):
|
||||
|
||||
if self.save_mcp_config(config):
|
||||
# 动态初始化新MCP客户端
|
||||
self.tool_mgr.mcp_service_queue.put_nowait(
|
||||
await self.tool_mgr.mcp_service_queue.put(
|
||||
{
|
||||
"type": "init",
|
||||
"name": name,
|
||||
@@ -166,9 +171,11 @@ class ToolsRoute(Route):
|
||||
|
||||
# 复制所有配置字段
|
||||
for key, value in server_data.items():
|
||||
if key not in ["name", "active", "tools"]: # 排除特殊字段
|
||||
if key not in ["name", "active", "tools", "errlogs"]: # 排除特殊字段
|
||||
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
|
||||
@@ -202,7 +209,7 @@ class ToolsRoute(Route):
|
||||
)
|
||||
else:
|
||||
# 客户端不存在,初始化
|
||||
self.tool_mgr.mcp_service_queue.put_nowait(
|
||||
await self.tool_mgr.mcp_service_queue.put(
|
||||
{
|
||||
"type": "init",
|
||||
"name": name,
|
||||
@@ -258,3 +265,26 @@ 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):
|
||||
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__
|
||||
@@ -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 }">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+606
-186
@@ -27,198 +27,348 @@
|
||||
</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-spacer></v-spacer>
|
||||
<v-btn color="primary" prepend-icon="mdi-refresh" variant="tonal" @click="getServers" :loading="loading">
|
||||
刷新
|
||||
</v-btn>
|
||||
<v-btn color="primary" style="margin-left: 8px;" 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">
|
||||
<div>
|
||||
<span class="text-h4 text-truncate" :title="server.name">{{ server.name }}</span>
|
||||
|
||||
<v-tooltip location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<btn class="text-caption text-medium-emphasis" v-if="server.errlogs" v-bind="props">
|
||||
<v-icon size="small" class="ms-1">mdi-information</v-icon>
|
||||
日志
|
||||
</btn>
|
||||
</template>
|
||||
<pre>{{ server.errlogs }}</pre>
|
||||
</v-tooltip>
|
||||
|
||||
</div>
|
||||
|
||||
<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
|
||||
@update:model-value="searchMarketplaceServers"
|
||||
></v-text-field>
|
||||
<v-btn color="primary" prepend-icon="mdi-refresh" variant="text" @click="fetchMarketplaceServers(1)" :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="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">暂无可用的 MCP 服务器</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">
|
||||
可用工具 ({{ 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>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<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>
|
||||
</v-container>
|
||||
|
||||
<!-- 添加/编辑 MCP 服务器对话框 -->
|
||||
@@ -277,6 +427,7 @@
|
||||
使用模板
|
||||
</v-btn>
|
||||
</div>
|
||||
<small>⚠ 某些 MCP 服务器可能需要按照其要求在 `env` 中填充 `API_KEY` 或 `TOKEN` 等信息,请注意检查是否填写。</small>
|
||||
|
||||
<div class="monaco-container">
|
||||
<VueMonacoEditor
|
||||
@@ -324,6 +475,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 +609,11 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'local', // 当前激活的标签页
|
||||
mcpServers: [],
|
||||
tools: [],
|
||||
showMcpServerDialog: false,
|
||||
showServerDetailDialog: false,
|
||||
showTools: true,
|
||||
loading: false,
|
||||
isEditMode: false,
|
||||
@@ -363,6 +629,19 @@ 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,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -399,23 +678,31 @@ export default {
|
||||
|
||||
return '未设置配置';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 过滤后的市场服务器
|
||||
filteredMarketplaceServers() {
|
||||
return this.marketplaceServers;
|
||||
},
|
||||
},
|
||||
|
||||
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
|
||||
@@ -430,11 +717,13 @@ export default {
|
||||
axios.get('/api/tools/mcp/servers')
|
||||
.then(response => {
|
||||
this.mcpServers = response.data.data || [];
|
||||
this.loading = false
|
||||
})
|
||||
.catch(error => {
|
||||
this.showError("获取 MCP 服务器列表失败: " + error.message);
|
||||
this.loading = false
|
||||
}).finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -533,9 +822,14 @@ export default {
|
||||
const configCopy = { ...server };
|
||||
|
||||
// 移除基本字段,只保留配置相关字段
|
||||
delete configCopy.name;
|
||||
delete configCopy.active;
|
||||
delete configCopy.tools;
|
||||
try {
|
||||
delete configCopy.name;
|
||||
delete configCopy.active;
|
||||
delete configCopy.tools;
|
||||
delete configCopy.errlogs;
|
||||
} catch (e) {
|
||||
console.error("Error removing basic fields: ", e);
|
||||
}
|
||||
|
||||
// 设置当前服务器的基本信息
|
||||
this.currentServer = {
|
||||
@@ -590,6 +884,117 @@ export default {
|
||||
this.save_message = message;
|
||||
this.save_message_success = "error";
|
||||
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("获取 MCP 市场服务器列表失败: " + 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 = '// 无可用配置';
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,6 +1058,21 @@ export default {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
height: 300px;
|
||||
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;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user