Files
claude-code/src/components/MCPServerDesktopImportDialog.tsx
2026-04-07 16:17:48 +08:00

132 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>
</>
)
}