mirror of
https://github.com/mfakbar127/Claude-Samurai
synced 2026-06-21 13:57:42 +00:00
prevent duplicated mcp name
This commit is contained in:
@@ -70,7 +70,7 @@ The app handles these configuration file types:
|
||||
- Place React Query/mutation logic in `src/lib/query.ts` by default
|
||||
- Write Tauri commands in `src-tauri/src/commands.rs` with well-designed names
|
||||
- Do not separate components into smaller files unless explicitly requested
|
||||
- Use `pnpm tsc --noEmit` to check TypeScript lint errors after modifying frontend code
|
||||
- Use `pnpm tsc --noEmit` instead of `pnpm tauri dev` to check TypeScript lint errors after modifying frontend code
|
||||
|
||||
## Important Notes
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"dialog:allow-ask"
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-message"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -629,6 +629,12 @@ pub async fn get_global_mcp_servers() -> Result<std::collections::HashMap<String
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_mcp_server_exists(server_name: String) -> Result<bool, String> {
|
||||
let mcp_servers = get_global_mcp_servers().await?;
|
||||
Ok(mcp_servers.contains_key(&server_name))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_global_mcp_server(
|
||||
server_name: String,
|
||||
@@ -669,6 +675,53 @@ pub async fn update_global_mcp_server(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_global_mcp_server(server_name: String) -> Result<(), String> {
|
||||
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
|
||||
let claude_json_path = home_dir.join(".claude.json");
|
||||
|
||||
if !claude_json_path.exists() {
|
||||
return Err("Claude configuration file does not exist".to_string());
|
||||
}
|
||||
|
||||
// Read existing .claude.json
|
||||
let content = std::fs::read_to_string(&claude_json_path)
|
||||
.map_err(|e| format!("Failed to read .claude.json: {}", e))?;
|
||||
|
||||
let mut json_value: Value = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse .claude.json: {}", e))?;
|
||||
|
||||
// Check if mcpServers exists
|
||||
let mcp_servers = json_value
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.get_mut("mcpServers")
|
||||
.and_then(|servers| servers.as_object_mut())
|
||||
.ok_or("No mcpServers found in .claude.json")?;
|
||||
|
||||
// Check if the server exists
|
||||
if !mcp_servers.contains_key(&server_name) {
|
||||
return Err(format!("MCP server '{}' not found", server_name));
|
||||
}
|
||||
|
||||
// Remove the server
|
||||
mcp_servers.remove(&server_name);
|
||||
|
||||
// If mcpServers is now empty, we can optionally remove the entire mcpServers object
|
||||
if mcp_servers.is_empty() {
|
||||
json_value.as_object_mut().unwrap().remove("mcpServers");
|
||||
}
|
||||
|
||||
// Write back to file
|
||||
let json_content = serde_json::to_string_pretty(&json_value)
|
||||
.map_err(|e| format!("Failed to serialize JSON: {}", e))?;
|
||||
|
||||
std::fs::write(&claude_json_path, json_content)
|
||||
.map_err(|e| format!("Failed to write .claude.json: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct UpdateInfo {
|
||||
pub available: bool,
|
||||
|
||||
@@ -159,6 +159,8 @@ pub fn run() {
|
||||
open_config_path,
|
||||
get_global_mcp_servers,
|
||||
update_global_mcp_server,
|
||||
delete_global_mcp_server,
|
||||
check_mcp_server_exists,
|
||||
check_for_updates,
|
||||
install_and_restart,
|
||||
rebuild_tray_menu_command
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
"csp": null,
|
||||
"capabilities": ["default"]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
||||
@@ -290,6 +290,30 @@ export const useAddGlobalMcpServer = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useCheckMcpServerExists = (serverName: string, options?: { enabled?: boolean }) => {
|
||||
return useQuery({
|
||||
queryKey: ["check-mcp-server-exists", serverName],
|
||||
queryFn: () => invoke<boolean>("check_mcp_server_exists", { serverName }),
|
||||
enabled: options?.enabled !== false && !!serverName, // Only run when serverName is provided
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteGlobalMcpServer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (serverName: string) => invoke<void>("delete_global_mcp_server", { serverName }),
|
||||
onSuccess: () => {
|
||||
toast.success("MCP server deleted successfully");
|
||||
queryClient.invalidateQueries({ queryKey: ["global-mcp-servers"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
toast.error(`Failed to delete MCP server: ${errorMessage}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to rebuild tray menu
|
||||
const rebuildTrayMenu = async () => {
|
||||
try {
|
||||
|
||||
+82
-9
@@ -2,15 +2,17 @@ import { useState, Suspense } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { ExternalLinkIcon, HammerIcon, PlusIcon, SaveIcon } from "lucide-react";
|
||||
import { useGlobalMcpServers, useUpdateGlobalMcpServer, type McpServer } from "@/lib/query";
|
||||
import { ExternalLinkIcon, HammerIcon, PlusIcon, SaveIcon, TrashIcon } from "lucide-react";
|
||||
import { useGlobalMcpServers, useUpdateGlobalMcpServer, useAddGlobalMcpServer, useDeleteGlobalMcpServer, type McpServer } from "@/lib/query";
|
||||
import { toast } from "sonner";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { ask, message } from "@tauri-apps/plugin-dialog";
|
||||
import { match } from "ts-pattern";
|
||||
|
||||
function MCPPageContent() {
|
||||
const { data: mcpServers } = useGlobalMcpServers();
|
||||
const updateMcpServer = useUpdateGlobalMcpServer();
|
||||
const deleteMcpServer = useDeleteGlobalMcpServer();
|
||||
const [serverConfigs, setServerConfigs] = useState<Record<string, string>>({});
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
@@ -36,6 +38,18 @@ function MCPPageContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteServer = async (serverName: string) => {
|
||||
// Show confirmation dialog
|
||||
const confirmed = await ask(
|
||||
`Are you sure you want to delete the MCP server "${serverName}"? This action cannot be undone.`,
|
||||
{ title: "Delete MCP Server", kind: "warning" }
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
deleteMcpServer.mutate(serverName);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const formatConfigForDisplay = (server: McpServer): string => {
|
||||
return JSON.stringify(server, null, 2);
|
||||
@@ -104,7 +118,7 @@ function MCPPageContent() {
|
||||
className="min-h-[100px] w-full p-2 text-md outline-none resize-none bg-white rounded-lg"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className="flex bg-zinc-50">
|
||||
<div className="flex justify-between bg-zinc-50">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleSaveConfig(serverName)}
|
||||
@@ -114,6 +128,15 @@ function MCPPageContent() {
|
||||
<SaveIcon size={14} className="" />
|
||||
{updateMcpServer.isPending ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteServer(serverName)}
|
||||
disabled={deleteMcpServer.isPending}
|
||||
>
|
||||
<TrashIcon size={14} className="" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
@@ -163,11 +186,14 @@ const builtInMcpServers = [
|
||||
},
|
||||
{
|
||||
name: "github",
|
||||
source: "https://github.com/github/github-mcp-server",
|
||||
source: "https://github.com/github/github-mcp-server/blob/main/docs/installation-guides/install-claude.md",
|
||||
description: "GitHub's official MCP Server",
|
||||
prefill: `"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/"
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Bearer <YOUR_GITHUB_TOKEN>"
|
||||
}
|
||||
}`
|
||||
}
|
||||
];
|
||||
@@ -210,16 +236,63 @@ function MCPCreatePanel() {
|
||||
}
|
||||
|
||||
function RecommendMCPPanel() {
|
||||
const addMcpServer = useAddGlobalMcpServer();
|
||||
const { data: mcpServers } = useGlobalMcpServers();
|
||||
|
||||
const handleAddMcpServer = async (mcpServer: typeof builtInMcpServers[0]) => {
|
||||
try {
|
||||
// Check if MCP server already exists using cached data
|
||||
const exists = mcpServers && Object.keys(mcpServers).includes(mcpServer.name);
|
||||
|
||||
if (exists) {
|
||||
await message(`MCP server "${mcpServer.name}" already exists`, {
|
||||
title: "MCP Server Exists",
|
||||
kind: "info"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
const confirmed = await ask(
|
||||
`Do you want to add the ${mcpServer.name} MCP server?`,
|
||||
{ title: "Add MCP Server", kind: "info" }
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
// Parse the prefill JSON to get the config object
|
||||
const configObject = JSON.parse(`{${mcpServer.prefill}}`);
|
||||
|
||||
addMcpServer.mutate({
|
||||
serverName: mcpServer.name,
|
||||
serverConfig: configObject[mcpServer.name]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to add MCP server:", error);
|
||||
// Only show toast for unexpected errors, not duplicate server errors
|
||||
if (error instanceof Error && !error.message.includes("already exists")) {
|
||||
toast.error("Failed to add MCP server");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-5">
|
||||
{builtInMcpServers.map((mcpServer) => (
|
||||
<a role="button" key={mcpServer.name} className="border p-3 rounded-md h-[120px] flex justify-between flex-col hover:bg-primary/10 hover:border-primary/20 hover:text-primary">
|
||||
<div
|
||||
key={mcpServer.name}
|
||||
className="border p-3 rounded-md h-[120px] flex justify-between flex-col hover:bg-primary/10 hover:border-primary/20 hover:text-primary cursor-pointer"
|
||||
onClick={() => handleAddMcpServer(mcpServer)}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="font-bold text-primary">{mcpServer.name}</h3>
|
||||
<a onClick={e => {
|
||||
<a
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
openUrl(mcpServer.source)
|
||||
}} className="text-sm text-muted-foreground flex items-center gap-1 hover:underline">
|
||||
}}
|
||||
className="text-sm text-muted-foreground flex items-center gap-1 hover:underline"
|
||||
>
|
||||
<ExternalLinkIcon size={12} />
|
||||
Source
|
||||
</a>
|
||||
@@ -234,7 +307,7 @@ function RecommendMCPPanel() {
|
||||
添加
|
||||
</Button> */}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user