feat(mcp): import Codex MCP from ~/.codex/config.toml
- Support both TOML schemas: [mcp.servers.<id>] and [mcp_servers.<id>] - Non-destructive merge of imported servers (enabled=true only) - Preserve existing TOML schema when syncing (prefer mcp_servers) - Remove both mcp and mcp_servers when no enabled items feat(ui): auto-import Codex MCP on panel init (app=codex) chore(tauri): add import_mcp_from_codex command and register chore(types): expose window.api.importMcpFromCodex and typings fix(ui): remove unused variable for typecheck
This commit is contained in:
@@ -23,7 +23,7 @@ function App() {
|
||||
const [currentProviderId, setCurrentProviderId] = useState<string>("");
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [editingProviderId, setEditingProviderId] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [notification, setNotification] = useState<{
|
||||
message: string;
|
||||
@@ -44,7 +44,7 @@ function App() {
|
||||
const showNotification = (
|
||||
message: string,
|
||||
type: "success" | "error",
|
||||
duration = 3000
|
||||
duration = 3000,
|
||||
) => {
|
||||
// 清除之前的定时器
|
||||
if (timeoutRef.current) {
|
||||
@@ -196,7 +196,7 @@ function App() {
|
||||
? t("notifications.removedFromClaudePlugin")
|
||||
: t("notifications.appliedToClaudePlugin"),
|
||||
"success",
|
||||
2000
|
||||
2000,
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -219,7 +219,7 @@ function App() {
|
||||
showNotification(
|
||||
t("notifications.switchSuccess", { appName }),
|
||||
"success",
|
||||
2000
|
||||
2000,
|
||||
);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
|
||||
@@ -44,7 +44,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
const { t } = useTranslation();
|
||||
const [formId, setFormId] = useState(editingId || "");
|
||||
const [formDescription, setFormDescription] = useState(
|
||||
(initialData as any)?.description || ""
|
||||
(initialData as any)?.description || "",
|
||||
);
|
||||
const [formJson, setFormJson] = useState(
|
||||
initialData ? JSON.stringify(initialData, null, 2) : "",
|
||||
|
||||
@@ -51,9 +51,11 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify, appType }) => {
|
||||
useEffect(() => {
|
||||
const setup = async () => {
|
||||
try {
|
||||
// Claude:从 ~/.claude.json 导入已存在的 MCP(设为 enabled=true)
|
||||
// 初始化导入:按应用类型从对应客户端导入已有 MCP(设为 enabled=true)
|
||||
if (appType === "claude") {
|
||||
await window.api.importMcpFromClaude();
|
||||
} else if (appType === "codex") {
|
||||
await window.api.importMcpFromCodex();
|
||||
}
|
||||
|
||||
// 读取现有 config.json 内容
|
||||
@@ -86,7 +88,11 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify, appType }) => {
|
||||
if (!server) {
|
||||
const preset = mcpPresets.find((p) => p.id === id);
|
||||
if (!preset) return;
|
||||
await window.api.upsertMcpServerInConfig(appType, id, preset.server as McpServer);
|
||||
await window.api.upsertMcpServerInConfig(
|
||||
appType,
|
||||
id,
|
||||
preset.server as McpServer,
|
||||
);
|
||||
}
|
||||
await window.api.setMcpEnabled(appType, id, enabled);
|
||||
await reload();
|
||||
@@ -177,7 +183,8 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify, appType }) => {
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-800">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
{t("mcp.title")} · {t(appType === "claude" ? "apps.claude" : "apps.codex")}
|
||||
{t("mcp.title")} ·{" "}
|
||||
{t(appType === "claude" ? "apps.claude" : "apps.codex")}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -252,10 +259,6 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify, appType }) => {
|
||||
|
||||
{/* 预设(未安装) */}
|
||||
{notInstalledPresets.map((p) => {
|
||||
const s = {
|
||||
...(p.server as McpServer),
|
||||
enabled: false,
|
||||
} as McpServer;
|
||||
return (
|
||||
<div
|
||||
key={`preset-${p.id}`}
|
||||
|
||||
@@ -17,8 +17,7 @@ export const buttonStyles = {
|
||||
"px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 transition-colors text-sm font-medium",
|
||||
|
||||
// MCP 专属按钮:绿底白字
|
||||
mcp:
|
||||
"px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700 transition-colors text-sm font-medium",
|
||||
mcp: "px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700 transition-colors text-sm font-medium",
|
||||
|
||||
// 幽灵按钮:无背景,仅悬浮反馈
|
||||
ghost:
|
||||
|
||||
@@ -355,7 +355,11 @@ export const tauriAPI = {
|
||||
spec: McpServer | Record<string, any>,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
return await invoke<boolean>("upsert_mcp_server_in_config", { app, id, spec });
|
||||
return await invoke<boolean>("upsert_mcp_server_in_config", {
|
||||
app,
|
||||
id,
|
||||
spec,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("写入 MCP(config.json)失败:", error);
|
||||
throw error;
|
||||
@@ -415,6 +419,16 @@ export const tauriAPI = {
|
||||
}
|
||||
},
|
||||
|
||||
// 从 ~/.codex/config.toml 导入 MCP(Codex 作用域)
|
||||
importMcpFromCodex: async (): Promise<number> => {
|
||||
try {
|
||||
return await invoke<number>("import_mcp_from_codex");
|
||||
} catch (error) {
|
||||
console.error("从 ~/.codex/config.toml 导入 MCP 失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ours: 第三方/自定义供应商——测速与端点管理
|
||||
// 第三方/自定义供应商:批量测试端点延迟
|
||||
testApiEndpoints: async (
|
||||
|
||||
14
src/vite-env.d.ts
vendored
14
src/vite-env.d.ts
vendored
@@ -1,6 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
import { Provider, Settings, CustomEndpoint, McpStatus, McpConfigResponse } from "./types";
|
||||
import {
|
||||
Provider,
|
||||
Settings,
|
||||
CustomEndpoint,
|
||||
McpStatus,
|
||||
McpConfigResponse,
|
||||
} from "./types";
|
||||
import { AppType } from "./lib/tauri-api";
|
||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
@@ -77,7 +83,10 @@ declare global {
|
||||
id: string,
|
||||
spec: Record<string, any>,
|
||||
) => Promise<boolean>;
|
||||
deleteMcpServerInConfig: (app: AppType | undefined, id: string) => Promise<boolean>;
|
||||
deleteMcpServerInConfig: (
|
||||
app: AppType | undefined,
|
||||
id: string,
|
||||
) => Promise<boolean>;
|
||||
setMcpEnabled: (
|
||||
app: AppType | undefined,
|
||||
id: string,
|
||||
@@ -86,6 +95,7 @@ declare global {
|
||||
syncEnabledMcpToClaude: () => Promise<boolean>;
|
||||
syncEnabledMcpToCodex: () => Promise<boolean>;
|
||||
importMcpFromClaude: () => Promise<number>;
|
||||
importMcpFromCodex: () => Promise<number>;
|
||||
testApiEndpoints: (
|
||||
urls: string[],
|
||||
options?: { timeoutSecs?: number },
|
||||
|
||||
Reference in New Issue
Block a user