import figures from 'figures'
import React, { useState } from 'react'
import type { CommandResultDisplay } from '../../commands.js'
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'
import { Box, color, Text, useTheme } from '@anthropic/ink'
import { getMcpConfigByName } from '../../services/mcp/config.js'
import {
useMcpReconnect,
useMcpToggleEnabled,
} from '../../services/mcp/MCPConnectionManager.js'
import {
describeMcpConfigFilePath,
filterMcpPromptsByServer,
} from '../../services/mcp/utils.js'
import { useAppState } from '../../state/AppState.js'
import { errorMessage } from '../../utils/errors.js'
import { capitalize } from '../../utils/stringUtils.js'
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'
import { Select } from '../CustomSelect/index.js'
import { Byline, KeyboardShortcutHint } from '@anthropic/ink'
import { Spinner } from '../Spinner.js'
import { CapabilitiesSection } from './CapabilitiesSection.js'
import type { StdioServerInfo } from './types.js'
import {
handleReconnectError,
handleReconnectResult,
} from './utils/reconnectHelpers.js'
type Props = {
server: StdioServerInfo
serverToolsCount: number
onViewTools: () => void
onCancel: () => void
onComplete: (
result?: string,
options?: { display?: CommandResultDisplay },
) => void
borderless?: boolean
}
export function MCPStdioServerMenu({
server,
serverToolsCount,
onViewTools,
onCancel,
onComplete,
borderless = false,
}: Props): React.ReactNode {
const [theme] = useTheme()
const exitState = useExitOnCtrlCDWithKeybindings()
const mcp = useAppState(s => s.mcp)
const reconnectMcpServer = useMcpReconnect()
const toggleMcpServer = useMcpToggleEnabled()
const [isReconnecting, setIsReconnecting] = useState(false)
const handleToggleEnabled = React.useCallback(async () => {
const wasEnabled = server.client.type !== 'disabled'
try {
await toggleMcpServer(server.name)
// Return to the server list so user can continue managing other servers
onCancel()
} catch (err) {
const action = wasEnabled ? 'disable' : 'enable'
onComplete(
`Failed to ${action} MCP server '${server.name}': ${errorMessage(err)}`,
)
}
}, [server.client.type, server.name, toggleMcpServer, onCancel, onComplete])
const capitalizedServerName = capitalize(String(server.name))
// Count MCP prompts for this server (skills are shown in /skills, not here)
const serverCommandsCount = filterMcpPromptsByServer(
mcp.commands,
server.name,
).length
const menuOptions = []
// Only show "View tools" if server is not disabled and has tools
if (server.client.type !== 'disabled' && serverToolsCount > 0) {
menuOptions.push({
label: 'View tools',
value: 'tools',
})
}
// Only show reconnect option if the server is not disabled
if (server.client.type !== 'disabled') {
menuOptions.push({
label: 'Reconnect',
value: 'reconnectMcpServer',
})
}
menuOptions.push({
label: server.client.type !== 'disabled' ? 'Disable' : 'Enable',
value: 'toggle-enabled',
})
// If there are no other options, add a back option so Select handles escape
if (menuOptions.length === 0) {
menuOptions.push({
label: 'Back',
value: 'back',
})
}
if (isReconnecting) {
return (
Reconnecting to {server.name}
Restarting MCP server process
This may take a few moments.
)
}
return (
{capitalizedServerName} MCP Server
Status:
{server.client.type === 'disabled' ? (
{color('inactive', theme)(figures.radioOff)} disabled
) : server.client.type === 'connected' ? (
{color('success', theme)(figures.tick)} connected
) : server.client.type === 'pending' ? (
<>
{figures.radioOff}
connecting…
>
) : (
{color('error', theme)(figures.cross)} failed
)}
Command:
{server.config.command}
{server.config.args && server.config.args.length > 0 && (
Args:
{server.config.args.join(' ')}
)}
Config location:
{describeMcpConfigFilePath(
getMcpConfigByName(server.name)?.scope ?? 'dynamic',
)}
{server.client.type === 'connected' && (
)}
{server.client.type === 'connected' && serverToolsCount > 0 && (
Tools:
{serverToolsCount} tools
)}
{menuOptions.length > 0 && (
)}
{exitState.pending ? (
<>Press {exitState.keyName} again to exit>
) : (
)}
)
}