Files
claude-code/src/components/MCPServerDesktopImportDialog.tsx
2026-05-01 21:39:30 +08:00

115 lines
3.9 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react';
import { gracefulShutdown } from 'src/utils/gracefulShutdown.js';
import { writeToStdout } from 'src/utils/process.js';
import { Box, color, Text, useTheme, Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink';
import { addMcpConfig, getAllMcpConfigs } from '../services/mcp/config.js';
import type { ConfigScope, McpServerConfig, ScopedMcpServerConfig } from '../services/mcp/types.js';
import { plural } from '../utils/stringUtils.js';
import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js';
import { SelectMulti } from './CustomSelect/SelectMulti.js';
type Props = {
servers: Record<string, McpServerConfig>;
scope: ConfigScope;
onDone(): void;
};
export function MCPServerDesktopImportDialog({ servers, scope, onDone }: Props): React.ReactNode {
const serverNames = Object.keys(servers);
const [existingServers, setExistingServers] = useState<Record<string, ScopedMcpServerConfig>>({});
useEffect(() => {
void getAllMcpConfigs().then(({ servers }) => setExistingServers(servers));
}, []);
const collisions = serverNames.filter(name => existingServers[name] !== undefined);
async function onSubmit(selectedServers: string[]) {
let importedCount = 0;
for (const serverName of selectedServers) {
const serverConfig = servers[serverName];
if (serverConfig) {
// If the server name already exists, find a new name with _1, _2, etc.
let finalName = serverName;
if (existingServers[finalName] !== undefined) {
let counter = 1;
while (existingServers[`${serverName}_${counter}`] !== undefined) {
counter++;
}
finalName = `${serverName}_${counter}`;
}
await addMcpConfig(finalName, serverConfig, scope);
importedCount++;
}
}
done(importedCount);
}
const [theme] = useTheme();
// Define done before using in useCallback
const done = useCallback(
(importedCount: number) => {
if (importedCount > 0) {
writeToStdout(
`\n${color('success', theme)(`Successfully imported ${importedCount} MCP ${plural(importedCount, 'server')} to ${scope} config.`)}\n`,
);
} else {
writeToStdout('\nNo servers were imported.');
}
onDone();
void gracefulShutdown();
},
[theme, scope, onDone],
);
// Handle ESC to cancel (import 0 servers)
const handleEscCancel = useCallback(() => {
done(0);
}, [done]);
return (
<>
<Dialog
title="Import MCP Servers from Claude Desktop"
subtitle={`Found ${serverNames.length} MCP ${plural(serverNames.length, 'server')} in Claude Desktop.`}
color="success"
onCancel={handleEscCancel}
hideInputGuide
>
{collisions.length > 0 && (
<Text color="warning">
Note: Some servers already exist with the same name. If selected, they will be imported with a numbered
suffix.
</Text>
)}
<Text>Please select the servers you want to import:</Text>
<SelectMulti
options={serverNames.map(server => ({
label: `${server}${collisions.includes(server) ? ' (already exists)' : ''}`,
value: server,
}))}
defaultValue={serverNames.filter(name => !collisions.includes(name))} // Only preselect non-colliding servers
onSubmit={onSubmit}
onCancel={handleEscCancel}
hideIndexes
/>
</Dialog>
<Box paddingX={1}>
<Text dimColor italic>
<Byline>
<KeyboardShortcutHint shortcut="Space" action="select" />
<KeyboardShortcutHint shortcut="Enter" action="confirm" />
<ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="cancel" />
</Byline>
</Text>
</Box>
</>
);
}