|
|
@@ -7,22 +7,29 @@ import CloseIcon from "../icons/close.svg";
|
|
|
import DeleteIcon from "../icons/delete.svg";
|
|
|
import RestartIcon from "../icons/reload.svg";
|
|
|
import EyeIcon from "../icons/eye.svg";
|
|
|
+import GithubIcon from "../icons/github.svg";
|
|
|
import { List, ListItem, Modal, showToast } from "./ui-lib";
|
|
|
import { useNavigate } from "react-router-dom";
|
|
|
-import { useState, useEffect } from "react";
|
|
|
+import { useEffect, useState } from "react";
|
|
|
import presetServersJson from "../mcp/preset-server.json";
|
|
|
-const presetServers = presetServersJson as PresetServer[];
|
|
|
import {
|
|
|
- getMcpConfig,
|
|
|
- updateMcpConfig,
|
|
|
- getClientPrimitives,
|
|
|
+ addMcpServer,
|
|
|
+ getClientStatus,
|
|
|
+ getClientTools,
|
|
|
+ getMcpConfigFromFile,
|
|
|
+ removeMcpServer,
|
|
|
restartAllClients,
|
|
|
- getClientErrors,
|
|
|
- refreshClientStatus,
|
|
|
} from "../mcp/actions";
|
|
|
-import { McpConfig, PresetServer, ServerConfig } from "../mcp/types";
|
|
|
+import {
|
|
|
+ ListToolsResponse,
|
|
|
+ McpConfigData,
|
|
|
+ PresetServer,
|
|
|
+ ServerConfig,
|
|
|
+} from "../mcp/types";
|
|
|
import clsx from "clsx";
|
|
|
|
|
|
+const presetServers = presetServersJson as PresetServer[];
|
|
|
+
|
|
|
interface ConfigProperty {
|
|
|
type: string;
|
|
|
description?: string;
|
|
|
@@ -33,67 +40,71 @@ interface ConfigProperty {
|
|
|
export function McpMarketPage() {
|
|
|
const navigate = useNavigate();
|
|
|
const [searchText, setSearchText] = useState("");
|
|
|
- const [config, setConfig] = useState<McpConfig>({ mcpServers: {} });
|
|
|
+ const [userConfig, setUserConfig] = useState<Record<string, any>>({});
|
|
|
const [editingServerId, setEditingServerId] = useState<string | undefined>();
|
|
|
+ const [tools, setTools] = useState<ListToolsResponse["tools"] | null>(null);
|
|
|
const [viewingServerId, setViewingServerId] = useState<string | undefined>();
|
|
|
- const [primitives, setPrimitives] = useState<any[]>([]);
|
|
|
- const [userConfig, setUserConfig] = useState<Record<string, any>>({});
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
- const [clientErrors, setClientErrors] = useState<
|
|
|
- Record<string, string | null>
|
|
|
+ const [config, setConfig] = useState<McpConfigData>();
|
|
|
+ const [clientStatuses, setClientStatuses] = useState<
|
|
|
+ Record<
|
|
|
+ string,
|
|
|
+ {
|
|
|
+ status: "active" | "error" | "undefined";
|
|
|
+ errorMsg: string | null;
|
|
|
+ }
|
|
|
+ >
|
|
|
>({});
|
|
|
|
|
|
- // 更新服务器状态
|
|
|
- const updateServerStatus = async () => {
|
|
|
- await refreshClientStatus();
|
|
|
- const errors = await getClientErrors();
|
|
|
- setClientErrors(errors);
|
|
|
+ // 检查服务器是否已添加
|
|
|
+ const isServerAdded = (id: string) => {
|
|
|
+ return id in (config?.mcpServers ?? {});
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取客户端状态
|
|
|
+ const updateClientStatus = async (clientId: string) => {
|
|
|
+ const status = await getClientStatus(clientId);
|
|
|
+ setClientStatuses((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ [clientId]: status,
|
|
|
+ }));
|
|
|
+ return status;
|
|
|
};
|
|
|
|
|
|
- // 初始加载配置
|
|
|
+ // 从服务器获取初始状态
|
|
|
useEffect(() => {
|
|
|
- const init = async () => {
|
|
|
+ const loadInitialState = async () => {
|
|
|
try {
|
|
|
setIsLoading(true);
|
|
|
- const data = await getMcpConfig();
|
|
|
- setConfig(data);
|
|
|
- await updateServerStatus();
|
|
|
+ const config = await getMcpConfigFromFile();
|
|
|
+ setConfig(config);
|
|
|
+
|
|
|
+ // 获取所有客户端的状态
|
|
|
+ const statuses: Record<string, any> = {};
|
|
|
+ for (const clientId of Object.keys(config.mcpServers)) {
|
|
|
+ const status = await getClientStatus(clientId);
|
|
|
+ statuses[clientId] = status;
|
|
|
+ }
|
|
|
+ setClientStatuses(statuses);
|
|
|
} catch (error) {
|
|
|
- showToast("Failed to load configuration");
|
|
|
- console.error(error);
|
|
|
+ console.error("Failed to load initial state:", error);
|
|
|
+ showToast("Failed to load initial state");
|
|
|
} finally {
|
|
|
setIsLoading(false);
|
|
|
}
|
|
|
};
|
|
|
- init().then();
|
|
|
+ loadInitialState();
|
|
|
}, []);
|
|
|
|
|
|
- // 保存配置
|
|
|
- const saveConfig = async (newConfig: McpConfig) => {
|
|
|
- try {
|
|
|
- setIsLoading(true);
|
|
|
- await updateMcpConfig(newConfig);
|
|
|
- setConfig(newConfig);
|
|
|
- // 配置改变时需要重新初始化
|
|
|
- await restartAllClients();
|
|
|
- await updateServerStatus();
|
|
|
- showToast("Configuration saved successfully");
|
|
|
- } catch (error) {
|
|
|
- showToast("Failed to save configuration");
|
|
|
- console.error(error);
|
|
|
- } finally {
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 检查服务器是否已添加
|
|
|
- const isServerAdded = (id: string) => {
|
|
|
- return id in config.mcpServers;
|
|
|
- };
|
|
|
+ // Debug: 监控状态变化
|
|
|
+ useEffect(() => {
|
|
|
+ console.log("MCP Market - Current config:", config);
|
|
|
+ console.log("MCP Market - Current clientStatuses:", clientStatuses);
|
|
|
+ }, [config, clientStatuses]);
|
|
|
|
|
|
// 加载当前编辑服务器的配置
|
|
|
useEffect(() => {
|
|
|
- if (editingServerId) {
|
|
|
+ if (editingServerId && config) {
|
|
|
const currentConfig = config.mcpServers[editingServerId];
|
|
|
if (currentConfig) {
|
|
|
// 从当前配置中提取用户配置
|
|
|
@@ -123,7 +134,7 @@ export function McpMarketPage() {
|
|
|
setUserConfig({});
|
|
|
}
|
|
|
}
|
|
|
- }, [editingServerId, config.mcpServers]);
|
|
|
+ }, [editingServerId, config]);
|
|
|
|
|
|
// 保存服务器配置
|
|
|
const saveServerConfig = async () => {
|
|
|
@@ -131,6 +142,7 @@ export function McpMarketPage() {
|
|
|
if (!preset || !preset.configSchema || !editingServerId) return;
|
|
|
|
|
|
try {
|
|
|
+ setIsLoading(true);
|
|
|
// 构建服务器配置
|
|
|
const args = [...preset.baseArgs];
|
|
|
const env: Record<string, string> = {};
|
|
|
@@ -160,22 +172,113 @@ export function McpMarketPage() {
|
|
|
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
|
};
|
|
|
|
|
|
- // 更新配置
|
|
|
- const newConfig = {
|
|
|
- ...config,
|
|
|
- mcpServers: {
|
|
|
- ...config.mcpServers,
|
|
|
- [editingServerId]: serverConfig,
|
|
|
- },
|
|
|
- };
|
|
|
+ // 更新配置并初始化新服务器
|
|
|
+ const newConfig = await addMcpServer(editingServerId, serverConfig);
|
|
|
+ setConfig(newConfig);
|
|
|
+
|
|
|
+ // 更新状态
|
|
|
+ const status = await getClientStatus(editingServerId);
|
|
|
+ setClientStatuses((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ [editingServerId]: status,
|
|
|
+ }));
|
|
|
|
|
|
- await saveConfig(newConfig);
|
|
|
setEditingServerId(undefined);
|
|
|
showToast("Server configuration saved successfully");
|
|
|
} catch (error) {
|
|
|
showToast(
|
|
|
error instanceof Error ? error.message : "Failed to save configuration",
|
|
|
);
|
|
|
+ } finally {
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取服务器支持的 Tools
|
|
|
+ const loadTools = async (id: string) => {
|
|
|
+ try {
|
|
|
+ const result = await getClientTools(id);
|
|
|
+ if (result) {
|
|
|
+ setTools(result);
|
|
|
+ } else {
|
|
|
+ throw new Error("Failed to load tools");
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ showToast("Failed to load tools");
|
|
|
+ console.error(error);
|
|
|
+ setTools(null);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重启所有客户端
|
|
|
+ const handleRestartAll = async () => {
|
|
|
+ try {
|
|
|
+ setIsLoading(true);
|
|
|
+ const newConfig = await restartAllClients();
|
|
|
+ setConfig(newConfig);
|
|
|
+
|
|
|
+ // 更新所有客户端状态
|
|
|
+ const statuses: Record<string, any> = {};
|
|
|
+ for (const clientId of Object.keys(newConfig.mcpServers)) {
|
|
|
+ const status = await getClientStatus(clientId);
|
|
|
+ statuses[clientId] = status;
|
|
|
+ }
|
|
|
+ setClientStatuses(statuses);
|
|
|
+
|
|
|
+ showToast("Successfully restarted all clients");
|
|
|
+ } catch (error) {
|
|
|
+ showToast("Failed to restart clients");
|
|
|
+ console.error(error);
|
|
|
+ } finally {
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加服务器
|
|
|
+ const addServer = async (preset: PresetServer) => {
|
|
|
+ if (!preset.configurable) {
|
|
|
+ try {
|
|
|
+ setIsLoading(true);
|
|
|
+ showToast("Creating MCP client...");
|
|
|
+ // 如果服务器不需要配置,直接添加
|
|
|
+ const serverConfig: ServerConfig = {
|
|
|
+ command: preset.command,
|
|
|
+ args: [...preset.baseArgs],
|
|
|
+ };
|
|
|
+ const newConfig = await addMcpServer(preset.id, serverConfig);
|
|
|
+ setConfig(newConfig);
|
|
|
+
|
|
|
+ // 更新状态
|
|
|
+ const status = await getClientStatus(preset.id);
|
|
|
+ setClientStatuses((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ [preset.id]: status,
|
|
|
+ }));
|
|
|
+ } finally {
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果需要配置,打开配置对话框
|
|
|
+ setEditingServerId(preset.id);
|
|
|
+ setUserConfig({});
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 移除服务器
|
|
|
+ const removeServer = async (id: string) => {
|
|
|
+ try {
|
|
|
+ setIsLoading(true);
|
|
|
+ const newConfig = await removeMcpServer(id);
|
|
|
+ setConfig(newConfig);
|
|
|
+
|
|
|
+ // 移除状态
|
|
|
+ setClientStatuses((prev) => {
|
|
|
+ const newStatuses = { ...prev };
|
|
|
+ delete newStatuses[id];
|
|
|
+ return newStatuses;
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ setIsLoading(false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -188,8 +291,17 @@ export function McpMarketPage() {
|
|
|
([key, prop]: [string, ConfigProperty]) => {
|
|
|
if (prop.type === "array") {
|
|
|
const currentValue = userConfig[key as keyof typeof userConfig] || [];
|
|
|
+ const itemLabel = (prop as any).itemLabel || key;
|
|
|
+ const addButtonText =
|
|
|
+ (prop as any).addButtonText || `Add ${itemLabel}`;
|
|
|
+
|
|
|
return (
|
|
|
- <ListItem key={key} title={key} subTitle={prop.description}>
|
|
|
+ <ListItem
|
|
|
+ key={key}
|
|
|
+ title={key}
|
|
|
+ subTitle={prop.description}
|
|
|
+ vertical
|
|
|
+ >
|
|
|
<div className={styles["path-list"]}>
|
|
|
{(currentValue as string[]).map(
|
|
|
(value: string, index: number) => (
|
|
|
@@ -197,7 +309,7 @@ export function McpMarketPage() {
|
|
|
<input
|
|
|
type="text"
|
|
|
value={value}
|
|
|
- placeholder={`Path ${index + 1}`}
|
|
|
+ placeholder={`${itemLabel} ${index + 1}`}
|
|
|
onChange={(e) => {
|
|
|
const newValue = [...currentValue] as string[];
|
|
|
newValue[index] = e.target.value;
|
|
|
@@ -218,7 +330,7 @@ export function McpMarketPage() {
|
|
|
)}
|
|
|
<IconButton
|
|
|
icon={<AddIcon />}
|
|
|
- text="Add Path"
|
|
|
+ text={addButtonText}
|
|
|
className={styles["add-button"]}
|
|
|
bordered
|
|
|
onClick={() => {
|
|
|
@@ -251,83 +363,146 @@ export function McpMarketPage() {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- // 获取服务器的 Primitives
|
|
|
- const loadPrimitives = async (id: string) => {
|
|
|
- try {
|
|
|
- setIsLoading(true);
|
|
|
- const result = await getClientPrimitives(id);
|
|
|
- if (result) {
|
|
|
- setPrimitives(result);
|
|
|
- } else {
|
|
|
- showToast("Server is not running");
|
|
|
- setPrimitives([]);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- showToast("Failed to load primitives");
|
|
|
- console.error(error);
|
|
|
- setPrimitives([]);
|
|
|
- } finally {
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
+ // 检查服务器状态
|
|
|
+ const checkServerStatus = (clientId: string) => {
|
|
|
+ return clientStatuses[clientId] || { status: "undefined", errorMsg: null };
|
|
|
};
|
|
|
|
|
|
- // 重启所有客户端
|
|
|
- const handleRestart = async () => {
|
|
|
- try {
|
|
|
- setIsLoading(true);
|
|
|
- await restartAllClients();
|
|
|
- await updateServerStatus();
|
|
|
- showToast("All clients restarted successfully");
|
|
|
- } catch (error) {
|
|
|
- showToast("Failed to restart clients");
|
|
|
- console.error(error);
|
|
|
- } finally {
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
- };
|
|
|
+ // 渲染服务器列表
|
|
|
+ const renderServerList = () => {
|
|
|
+ return presetServers
|
|
|
+ .filter((server) => {
|
|
|
+ if (searchText.length === 0) return true;
|
|
|
+ const searchLower = searchText.toLowerCase();
|
|
|
+ return (
|
|
|
+ server.name.toLowerCase().includes(searchLower) ||
|
|
|
+ server.description.toLowerCase().includes(searchLower) ||
|
|
|
+ server.tags.some((tag) => tag.toLowerCase().includes(searchLower))
|
|
|
+ );
|
|
|
+ })
|
|
|
+ .sort((a, b) => {
|
|
|
+ const aStatus = checkServerStatus(a.id).status;
|
|
|
+ const bStatus = checkServerStatus(b.id).status;
|
|
|
|
|
|
- // 添加服务器
|
|
|
- const addServer = async (preset: PresetServer) => {
|
|
|
- if (!preset.configurable) {
|
|
|
- try {
|
|
|
- setIsLoading(true);
|
|
|
- showToast("Creating MCP client...");
|
|
|
- // 如果服务器不需要配置,直接添加
|
|
|
- const serverConfig: ServerConfig = {
|
|
|
- command: preset.command,
|
|
|
- args: [...preset.baseArgs],
|
|
|
+ // 定义状态优先级
|
|
|
+ const statusPriority = {
|
|
|
+ error: 0,
|
|
|
+ active: 1,
|
|
|
+ undefined: 2,
|
|
|
};
|
|
|
- const newConfig = {
|
|
|
- ...config,
|
|
|
- mcpServers: {
|
|
|
- ...config.mcpServers,
|
|
|
- [preset.id]: serverConfig,
|
|
|
- },
|
|
|
- };
|
|
|
- await saveConfig(newConfig);
|
|
|
- } finally {
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 如果需要配置,打开配置对话框
|
|
|
- setEditingServerId(preset.id);
|
|
|
- setUserConfig({});
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
- // 移除服务器
|
|
|
- const removeServer = async (id: string) => {
|
|
|
- try {
|
|
|
- setIsLoading(true);
|
|
|
- const { [id]: _, ...rest } = config.mcpServers;
|
|
|
- const newConfig = {
|
|
|
- ...config,
|
|
|
- mcpServers: rest,
|
|
|
- };
|
|
|
- await saveConfig(newConfig);
|
|
|
- } finally {
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
+ // 首先按状态排序
|
|
|
+ if (aStatus !== bStatus) {
|
|
|
+ return statusPriority[aStatus] - statusPriority[bStatus];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 然后按名称排序
|
|
|
+ return a.name.localeCompare(b.name);
|
|
|
+ })
|
|
|
+ .map((server) => (
|
|
|
+ <div
|
|
|
+ className={clsx(styles["mcp-market-item"], {
|
|
|
+ [styles["disabled"]]: isLoading,
|
|
|
+ })}
|
|
|
+ key={server.id}
|
|
|
+ >
|
|
|
+ <div className={styles["mcp-market-header"]}>
|
|
|
+ <div className={styles["mcp-market-title"]}>
|
|
|
+ <div className={styles["mcp-market-name"]}>
|
|
|
+ {server.name}
|
|
|
+ {checkServerStatus(server.id).status !== "undefined" && (
|
|
|
+ <span
|
|
|
+ className={clsx(styles["server-status"], {
|
|
|
+ [styles["error"]]:
|
|
|
+ checkServerStatus(server.id).status === "error",
|
|
|
+ })}
|
|
|
+ >
|
|
|
+ {checkServerStatus(server.id).status === "error" ? (
|
|
|
+ <>
|
|
|
+ Error
|
|
|
+ <span className={styles["error-message"]}>
|
|
|
+ : {checkServerStatus(server.id).errorMsg}
|
|
|
+ </span>
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ "Active"
|
|
|
+ )}
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
+ {server.repo && (
|
|
|
+ <a
|
|
|
+ href={server.repo}
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer"
|
|
|
+ className={styles["repo-link"]}
|
|
|
+ title="Open repository"
|
|
|
+ >
|
|
|
+ <GithubIcon />
|
|
|
+ </a>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div className={styles["tags-container"]}>
|
|
|
+ {server.tags.map((tag, index) => (
|
|
|
+ <span key={index} className={styles["tag"]}>
|
|
|
+ {tag}
|
|
|
+ </span>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ className={clsx(styles["mcp-market-info"], "one-line")}
|
|
|
+ title={server.description}
|
|
|
+ >
|
|
|
+ {server.description}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles["mcp-market-actions"]}>
|
|
|
+ {isServerAdded(server.id) ? (
|
|
|
+ <>
|
|
|
+ {server.configurable && (
|
|
|
+ <IconButton
|
|
|
+ icon={<EditIcon />}
|
|
|
+ text="Configure"
|
|
|
+ className={clsx({
|
|
|
+ [styles["action-error"]]:
|
|
|
+ checkServerStatus(server.id).status === "error",
|
|
|
+ })}
|
|
|
+ onClick={() => setEditingServerId(server.id)}
|
|
|
+ disabled={isLoading}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ <IconButton
|
|
|
+ icon={<EyeIcon />}
|
|
|
+ text="Tools"
|
|
|
+ onClick={async () => {
|
|
|
+ setViewingServerId(server.id);
|
|
|
+ await loadTools(server.id);
|
|
|
+ }}
|
|
|
+ disabled={
|
|
|
+ isLoading ||
|
|
|
+ checkServerStatus(server.id).status === "error"
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <IconButton
|
|
|
+ icon={<DeleteIcon />}
|
|
|
+ text="Remove"
|
|
|
+ className={styles["action-danger"]}
|
|
|
+ onClick={() => removeServer(server.id)}
|
|
|
+ disabled={isLoading}
|
|
|
+ />
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <IconButton
|
|
|
+ icon={<AddIcon />}
|
|
|
+ text="Add"
|
|
|
+ className={styles["action-primary"]}
|
|
|
+ onClick={() => addServer(server)}
|
|
|
+ disabled={isLoading}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ));
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
@@ -342,7 +517,7 @@ export function McpMarketPage() {
|
|
|
)}
|
|
|
</div>
|
|
|
<div className="window-header-sub-title">
|
|
|
- {Object.keys(config.mcpServers).length} servers configured
|
|
|
+ {Object.keys(config?.mcpServers ?? {}).length} servers configured
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -351,7 +526,7 @@ export function McpMarketPage() {
|
|
|
<IconButton
|
|
|
icon={<RestartIcon />}
|
|
|
bordered
|
|
|
- onClick={handleRestart}
|
|
|
+ onClick={handleRestartAll}
|
|
|
text="Restart All"
|
|
|
disabled={isLoading}
|
|
|
/>
|
|
|
@@ -378,121 +553,10 @@ export function McpMarketPage() {
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
- <div className={styles["server-list"]}>
|
|
|
- {presetServers
|
|
|
- .filter(
|
|
|
- (m) =>
|
|
|
- searchText.length === 0 ||
|
|
|
- m.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
|
- m.description
|
|
|
- .toLowerCase()
|
|
|
- .includes(searchText.toLowerCase()),
|
|
|
- )
|
|
|
- .sort((a, b) => {
|
|
|
- const aAdded = isServerAdded(a.id);
|
|
|
- const bAdded = isServerAdded(b.id);
|
|
|
- const aError = clientErrors[a.id] !== null;
|
|
|
- const bError = clientErrors[b.id] !== null;
|
|
|
-
|
|
|
- if (aAdded !== bAdded) {
|
|
|
- return aAdded ? -1 : 1;
|
|
|
- }
|
|
|
- if (aAdded && bAdded) {
|
|
|
- if (aError !== bError) {
|
|
|
- return aError ? -1 : 1;
|
|
|
- }
|
|
|
- }
|
|
|
- return 0;
|
|
|
- })
|
|
|
- .map((server) => (
|
|
|
- <div
|
|
|
- className={clsx(styles["mcp-market-item"], {
|
|
|
- [styles["disabled"]]: isLoading,
|
|
|
- })}
|
|
|
- key={server.id}
|
|
|
- >
|
|
|
- <div className={styles["mcp-market-header"]}>
|
|
|
- <div className={styles["mcp-market-title"]}>
|
|
|
- <div className={styles["mcp-market-name"]}>
|
|
|
- {server.name}
|
|
|
- {isServerAdded(server.id) && (
|
|
|
- <span
|
|
|
- className={clsx(styles["server-status"], {
|
|
|
- [styles["error"]]:
|
|
|
- clientErrors[server.id] !== null,
|
|
|
- })}
|
|
|
- >
|
|
|
- {clientErrors[server.id] === null
|
|
|
- ? "Active"
|
|
|
- : "Error"}
|
|
|
- {clientErrors[server.id] && (
|
|
|
- <span className={styles["error-message"]}>
|
|
|
- : {clientErrors[server.id]}
|
|
|
- </span>
|
|
|
- )}
|
|
|
- </span>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- <div
|
|
|
- className={clsx(styles["mcp-market-info"], "one-line")}
|
|
|
- >
|
|
|
- {server.description}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div className={styles["mcp-market-actions"]}>
|
|
|
- {isServerAdded(server.id) ? (
|
|
|
- <>
|
|
|
- {server.configurable && (
|
|
|
- <IconButton
|
|
|
- icon={<EditIcon />}
|
|
|
- text="Configure"
|
|
|
- className={clsx({
|
|
|
- [styles["action-error"]]:
|
|
|
- clientErrors[server.id] !== null,
|
|
|
- })}
|
|
|
- onClick={() => setEditingServerId(server.id)}
|
|
|
- disabled={isLoading}
|
|
|
- />
|
|
|
- )}
|
|
|
- {isServerAdded(server.id) && (
|
|
|
- <IconButton
|
|
|
- icon={<EyeIcon />}
|
|
|
- text="Tools"
|
|
|
- onClick={async () => {
|
|
|
- if (clientErrors[server.id] !== null) {
|
|
|
- showToast("Server is not running");
|
|
|
- return;
|
|
|
- }
|
|
|
- setViewingServerId(server.id);
|
|
|
- await loadPrimitives(server.id);
|
|
|
- }}
|
|
|
- disabled={isLoading}
|
|
|
- />
|
|
|
- )}
|
|
|
- <IconButton
|
|
|
- icon={<DeleteIcon />}
|
|
|
- text="Remove"
|
|
|
- className={styles["action-danger"]}
|
|
|
- onClick={() => removeServer(server.id)}
|
|
|
- disabled={isLoading}
|
|
|
- />
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- <IconButton
|
|
|
- icon={<AddIcon />}
|
|
|
- text="Add"
|
|
|
- className={styles["action-primary"]}
|
|
|
- onClick={() => addServer(server)}
|
|
|
- disabled={isLoading}
|
|
|
- />
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
+ <div className={styles["server-list"]}>{renderServerList()}</div>
|
|
|
</div>
|
|
|
|
|
|
+ {/*编辑服务器配置*/}
|
|
|
{editingServerId && (
|
|
|
<div className="modal-mask">
|
|
|
<Modal
|
|
|
@@ -521,6 +585,7 @@ export function McpMarketPage() {
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
+ {/*支持的Tools*/}
|
|
|
{viewingServerId && (
|
|
|
<div className="modal-mask">
|
|
|
<Modal
|
|
|
@@ -535,24 +600,20 @@ export function McpMarketPage() {
|
|
|
/>,
|
|
|
]}
|
|
|
>
|
|
|
- <div className={styles["primitives-list"]}>
|
|
|
+ <div className={styles["tools-list"]}>
|
|
|
{isLoading ? (
|
|
|
<div>Loading...</div>
|
|
|
- ) : primitives.filter((p) => p.type === "tool").length > 0 ? (
|
|
|
- primitives
|
|
|
- .filter((p) => p.type === "tool")
|
|
|
- .map((primitive, index) => (
|
|
|
- <div key={index} className={styles["primitive-item"]}>
|
|
|
- <div className={styles["primitive-name"]}>
|
|
|
- {primitive.value.name}
|
|
|
+ ) : tools?.tools ? (
|
|
|
+ tools.tools.map(
|
|
|
+ (tool: ListToolsResponse["tools"], index: number) => (
|
|
|
+ <div key={index} className={styles["tool-item"]}>
|
|
|
+ <div className={styles["tool-name"]}>{tool.name}</div>
|
|
|
+ <div className={styles["tool-description"]}>
|
|
|
+ {tool.description}
|
|
|
</div>
|
|
|
- {primitive.value.description && (
|
|
|
- <div className={styles["primitive-description"]}>
|
|
|
- {primitive.value.description}
|
|
|
- </div>
|
|
|
- )}
|
|
|
</div>
|
|
|
- ))
|
|
|
+ ),
|
|
|
+ )
|
|
|
) : (
|
|
|
<div>No tools available</div>
|
|
|
)}
|