diff --git a/src/components/mcp/constants.ts b/src/components/mcp/constants.ts index fb7a5793..4b1a949c 100644 --- a/src/components/mcp/constants.ts +++ b/src/components/mcp/constants.ts @@ -21,6 +21,10 @@ export const MCP_SUPPORTED_TRANSPORTS: Record = { gemini: ['stdio', 'http', 'sse'], }; +export const MCP_GLOBAL_SUPPORTED_SCOPES: McpScope[] = ['user', 'project']; + +export const MCP_GLOBAL_SUPPORTED_TRANSPORTS: McpTransport[] = ['stdio', 'http']; + export const MCP_PROVIDER_BUTTON_CLASSES: Record = { claude: 'bg-purple-600 text-white hover:bg-purple-700', cursor: 'bg-purple-600 text-white hover:bg-purple-700', diff --git a/src/components/mcp/hooks/useMcpServerForm.ts b/src/components/mcp/hooks/useMcpServerForm.ts index e9224de5..52809cbe 100644 --- a/src/components/mcp/hooks/useMcpServerForm.ts +++ b/src/components/mcp/hooks/useMcpServerForm.ts @@ -17,6 +17,9 @@ type UseMcpServerFormArgs = { isOpen: boolean; editingServer: ProviderMcpServer | null; currentProjects: McpProject[]; + supportedScopes?: McpScope[]; + supportedTransports?: McpTransport[]; + unsupportedTransportMessage?: (transport: McpTransport) => string; onSubmit: (formData: McpFormState, editingServer: ProviderMcpServer | null) => Promise; }; @@ -28,10 +31,14 @@ type MultilineFieldText = { envHttpHeaders: string; }; -const cloneDefaultForm = (provider: McpProvider): McpFormState => ({ +const cloneDefaultForm = ( + provider: McpProvider, + supportedScopes = MCP_SUPPORTED_SCOPES[provider], + supportedTransports = MCP_SUPPORTED_TRANSPORTS[provider], +): McpFormState => ({ ...DEFAULT_MCP_FORM, - scope: MCP_SUPPORTED_SCOPES[provider][0], - transport: MCP_SUPPORTED_TRANSPORTS[provider][0], + scope: supportedScopes[0], + transport: supportedTransports[0], args: [], env: {}, headers: {}, @@ -42,8 +49,10 @@ const cloneDefaultForm = (provider: McpProvider): McpFormState => ({ const createFormStateFromServer = ( provider: McpProvider, server: ProviderMcpServer, + supportedScopes?: McpScope[], + supportedTransports?: McpTransport[], ): McpFormState => ({ - ...cloneDefaultForm(provider), + ...cloneDefaultForm(provider, supportedScopes, supportedTransports), name: server.name, scope: server.scope, workspacePath: server.workspacePath || '', @@ -67,12 +76,12 @@ const createMultilineTextFromForm = (formData: McpFormState): MultilineFieldText envHttpHeaders: formatKeyValueLines(formData.envHttpHeaders), }); -const normalizeScope = (provider: McpProvider, value: McpScope): McpScope => ( - MCP_SUPPORTED_SCOPES[provider].includes(value) ? value : MCP_SUPPORTED_SCOPES[provider][0] +const normalizeScope = (supportedScopes: McpScope[], value: McpScope): McpScope => ( + supportedScopes.includes(value) ? value : supportedScopes[0] ); -const normalizeTransport = (provider: McpProvider, value: McpTransport): McpTransport => ( - MCP_SUPPORTED_TRANSPORTS[provider].includes(value) ? value : MCP_SUPPORTED_TRANSPORTS[provider][0] +const normalizeTransport = (supportedTransports: McpTransport[], value: McpTransport): McpTransport => ( + supportedTransports.includes(value) ? value : supportedTransports[0] ); export function useMcpServerForm({ @@ -80,12 +89,17 @@ export function useMcpServerForm({ isOpen, editingServer, currentProjects, + supportedScopes = MCP_SUPPORTED_SCOPES[provider], + supportedTransports = MCP_SUPPORTED_TRANSPORTS[provider], + unsupportedTransportMessage, onSubmit, }: UseMcpServerFormArgs) { const { t } = useTranslation('settings'); - const [formData, setFormData] = useState(() => cloneDefaultForm(provider)); + const [formData, setFormData] = useState(() => ( + cloneDefaultForm(provider, supportedScopes, supportedTransports) + )); const [multilineText, setMultilineText] = useState(() => ( - createMultilineTextFromForm(cloneDefaultForm(provider)) + createMultilineTextFromForm(cloneDefaultForm(provider, supportedScopes, supportedTransports)) )); const [jsonValidationError, setJsonValidationError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); @@ -99,16 +113,16 @@ export function useMcpServerForm({ setJsonValidationError(''); if (editingServer) { - const nextFormData = createFormStateFromServer(provider, editingServer); + const nextFormData = createFormStateFromServer(provider, editingServer, supportedScopes, supportedTransports); setFormData(nextFormData); setMultilineText(createMultilineTextFromForm(nextFormData)); return; } - const nextFormData = cloneDefaultForm(provider); + const nextFormData = cloneDefaultForm(provider, supportedScopes, supportedTransports); setFormData(nextFormData); setMultilineText(createMultilineTextFromForm(nextFormData)); - }, [editingServer, isOpen, provider]); + }, [editingServer, isOpen, provider, supportedScopes, supportedTransports]); const projectOptions = useMemo(() => ( currentProjects @@ -126,13 +140,13 @@ export function useMcpServerForm({ const updateScope = (scope: McpScope) => { setFormData((prev) => ({ ...prev, - scope: normalizeScope(provider, scope), + scope: normalizeScope(supportedScopes, scope), workspacePath: scope === 'user' ? '' : prev.workspacePath, })); }; const updateTransport = (transport: McpTransport) => { - setFormData((prev) => ({ ...prev, transport: normalizeTransport(provider, transport) })); + setFormData((prev) => ({ ...prev, transport: normalizeTransport(supportedTransports, transport) })); }; const validateJsonInput = (value: string) => { @@ -146,8 +160,10 @@ export function useMcpServerForm({ const transportInput = parsed.transport || parsed.type; if (!isMcpTransport(transportInput)) { setJsonValidationError(t('mcpForm.validation.missingType')); - } else if (!MCP_SUPPORTED_TRANSPORTS[provider].includes(transportInput)) { - setJsonValidationError(`${provider} does not support ${transportInput} MCP servers`); + } else if (!supportedTransports.includes(transportInput)) { + setJsonValidationError( + unsupportedTransportMessage?.(transportInput) ?? `${provider} does not support ${transportInput} MCP servers`, + ); } else if (transportInput === 'stdio' && !parsed.command) { setJsonValidationError(t('mcpForm.validation.stdioRequiresCommand')); } else if ((transportInput === 'http' || transportInput === 'sse') && !parsed.url) { diff --git a/src/components/mcp/hooks/useMcpServers.ts b/src/components/mcp/hooks/useMcpServers.ts index 631f1170..57ed81cc 100644 --- a/src/components/mcp/hooks/useMcpServers.ts +++ b/src/components/mcp/hooks/useMcpServers.ts @@ -1,9 +1,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { authenticatedFetch } from '../../../utils/api'; -import { MCP_SUPPORTED_SCOPES } from '../constants'; +import { MCP_GLOBAL_SUPPORTED_TRANSPORTS, MCP_PROVIDER_NAMES, MCP_SUPPORTED_SCOPES } from '../constants'; import type { ApiResponse, + GlobalMcpServerResult, McpFormState, McpProject, McpProvider, @@ -26,6 +27,10 @@ type ProviderMcpServerResponse = { servers: Array>; }; +type GlobalMcpServerResponse = { + results: GlobalMcpServerResult[]; +}; + type ProjectTarget = { name: string; displayName: string; @@ -184,6 +189,22 @@ const saveProviderServer = async ( } }; +const saveGlobalServer = async ( + payload: UpsertProviderMcpServerPayload, +): Promise => { + const response = await authenticatedFetch('/api/providers/mcp/servers/global', { + method: 'POST', + body: JSON.stringify(payload), + }); + const data = await toResponseJson>(response); + + if (!response.ok || !data.success) { + throw new Error(getApiErrorMessage(data, 'Failed to save MCP server to all providers')); + } + + return data.data.results || []; +}; + const didServerIdentityChange = ( editingServer: ProviderMcpServer, payload: UpsertProviderMcpServerPayload, @@ -202,6 +223,12 @@ const getCacheKey = (provider: McpProvider, projects: ProjectTarget[]): string = return `${provider}:${projectKey}`; }; +const formatGlobalAddFailures = (failures: GlobalMcpServerResult[]): string => ( + failures + .map((failure) => `${MCP_PROVIDER_NAMES[failure.provider]}: ${failure.error || 'Unknown error'}`) + .join('; ') +); + const sortServers = (servers: ProviderMcpServer[]): ProviderMcpServer[] => { const scopeOrder: Record = { user: 0, @@ -265,6 +292,7 @@ export function useMcpServers({ selectedProvider, currentProjects }: UseMcpServe const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null); const [isLoadingProjectScopes, setIsLoadingProjectScopes] = useState(false); const [isFormOpen, setIsFormOpen] = useState(false); + const [isGlobalFormOpen, setIsGlobalFormOpen] = useState(false); const [editingServer, setEditingServer] = useState(null); const activeLoadIdRef = useRef(0); @@ -379,6 +407,14 @@ export function useMcpServers({ selectedProvider, currentProjects }: UseMcpServe setEditingServer(null); }, []); + const openGlobalForm = useCallback(() => { + setIsGlobalFormOpen(true); + }, []); + + const closeGlobalForm = useCallback(() => { + setIsGlobalFormOpen(false); + }, []); + const submitForm = useCallback( async (formData: McpFormState, serverBeingEdited: ProviderMcpServer | null) => { const payload = createMcpPayloadFromForm(selectedProvider, formData); @@ -400,6 +436,42 @@ export function useMcpServers({ selectedProvider, currentProjects }: UseMcpServe [cacheKey, closeForm, refreshServers, selectedProvider], ); + const submitGlobalForm = useCallback( + async (formData: McpFormState) => { + const payload = createMcpPayloadFromForm(selectedProvider, formData, { + supportedTransports: MCP_GLOBAL_SUPPORTED_TRANSPORTS, + supportsWorkingDirectory: false, + includeProviderSpecificFields: false, + unsupportedTransportMessage: (transport) => + `Add MCP Server supports only stdio and http across all providers, not ${transport}.`, + }); + + if (payload.scope === 'local') { + throw new Error('Add MCP Server supports only user or project scope across all providers.'); + } + + if (payload.scope !== 'user' && !payload.workspacePath) { + throw new Error('Select a project for project-scoped MCP servers'); + } + + // The global endpoint updates every provider, so clear every provider + // cache entry instead of only the currently visible provider tab. + const results = await saveGlobalServer(payload); + mcpServersCache.clear(); + await refreshServers({ force: true }); + + const failures = results.filter((result) => !result.created); + if (failures.length > 0) { + setSaveStatus('error'); + throw new Error(`Failed to add MCP server to all providers. ${formatGlobalAddFailures(failures)}`); + } + + setSaveStatus('success'); + closeGlobalForm(); + }, + [closeGlobalForm, refreshServers, selectedProvider], + ); + const deleteServer = useCallback( async (server: ProviderMcpServer) => { if (!window.confirm('Are you sure you want to delete this MCP server?')) { @@ -426,6 +498,7 @@ export function useMcpServers({ selectedProvider, currentProjects }: UseMcpServe useEffect(() => { setIsFormOpen(false); + setIsGlobalFormOpen(false); setEditingServer(null); setDeleteError(null); setSaveStatus(null); @@ -448,10 +521,14 @@ export function useMcpServers({ selectedProvider, currentProjects }: UseMcpServe deleteError, saveStatus, isFormOpen, + isGlobalFormOpen, editingServer, openForm, + openGlobalForm, closeForm, + closeGlobalForm, submitForm, + submitGlobalForm, deleteServer, refreshServers, }; diff --git a/src/components/mcp/types.ts b/src/components/mcp/types.ts index 997ecef4..810258e9 100644 --- a/src/components/mcp/types.ts +++ b/src/components/mcp/types.ts @@ -4,6 +4,7 @@ export type McpProvider = LLMProvider; export type McpScope = 'user' | 'local' | 'project'; export type McpTransport = 'stdio' | 'http' | 'sse'; export type McpImportMode = 'form' | 'json'; +export type McpFormMode = 'provider' | 'global'; export type KeyValueMap = Record; export type McpProject = { @@ -66,6 +67,12 @@ export type UpsertProviderMcpServerPayload = { envHttpHeaders?: KeyValueMap; }; +export type GlobalMcpServerResult = { + provider: McpProvider; + created: boolean; + error?: string; +}; + export type ApiSuccessResponse = { success: true; data: T; diff --git a/src/components/mcp/utils/mcpFormatting.ts b/src/components/mcp/utils/mcpFormatting.ts index badda29a..4184c234 100644 --- a/src/components/mcp/utils/mcpFormatting.ts +++ b/src/components/mcp/utils/mcpFormatting.ts @@ -8,6 +8,13 @@ import type { UpsertProviderMcpServerPayload, } from '../types'; +type CreateMcpPayloadOptions = { + supportedTransports?: McpTransport[]; + supportsWorkingDirectory?: boolean; + includeProviderSpecificFields?: boolean; + unsupportedTransportMessage?: (transport: McpTransport) => string; +}; + const isRecord = (value: unknown): value is Record => ( Boolean(value) && typeof value === 'object' && !Array.isArray(value) ); @@ -79,9 +86,25 @@ export const getErrorMessage = (error: unknown): string => ( error instanceof Error ? error.message : 'Unknown error' ); +const assertSupportedTransport = ( + provider: McpProvider, + transport: McpTransport, + options?: CreateMcpPayloadOptions, +) => { + const supportedTransports = options?.supportedTransports ?? MCP_SUPPORTED_TRANSPORTS[provider]; + if (supportedTransports.includes(transport)) { + return; + } + + throw new Error( + options?.unsupportedTransportMessage?.(transport) ?? `${provider} does not support ${transport} MCP servers`, + ); +}; + export const parseJsonMcpPayload = ( provider: McpProvider, formData: McpFormState, + options?: CreateMcpPayloadOptions, ): UpsertProviderMcpServerPayload => { const parsed = JSON.parse(formData.jsonInput) as unknown; if (!isRecord(parsed)) { @@ -94,9 +117,7 @@ export const parseJsonMcpPayload = ( throw new Error('Missing required field: type'); } - if (!MCP_SUPPORTED_TRANSPORTS[provider].includes(transport)) { - throw new Error(`${provider} does not support ${transport} MCP servers`); - } + assertSupportedTransport(provider, transport, options); if (transport === 'stdio' && !readString(parsed.command)) { throw new Error('stdio type requires a command field'); @@ -114,23 +135,37 @@ export const parseJsonMcpPayload = ( command: readString(parsed.command), args: readStringArray(parsed.args) ?? [], env: readStringRecord(parsed.env) ?? {}, - cwd: MCP_SUPPORTS_WORKING_DIRECTORY[provider] ? readString(parsed.cwd) : undefined, + cwd: (options?.supportsWorkingDirectory ?? MCP_SUPPORTS_WORKING_DIRECTORY[provider]) + ? readString(parsed.cwd) + : undefined, url: readString(parsed.url), headers: readStringRecord(parsed.headers ?? parsed.http_headers) ?? {}, - envVars: readStringArray(parsed.envVars ?? parsed.env_vars) ?? [], - bearerTokenEnvVar: readString(parsed.bearerTokenEnvVar ?? parsed.bearer_token_env_var), - envHttpHeaders: readStringRecord(parsed.envHttpHeaders ?? parsed.env_http_headers) ?? {}, + envVars: (options?.includeProviderSpecificFields ?? provider === 'codex') + ? readStringArray(parsed.envVars ?? parsed.env_vars) ?? [] + : undefined, + bearerTokenEnvVar: (options?.includeProviderSpecificFields ?? provider === 'codex') + ? readString(parsed.bearerTokenEnvVar ?? parsed.bearer_token_env_var) + : undefined, + envHttpHeaders: (options?.includeProviderSpecificFields ?? provider === 'codex') + ? readStringRecord(parsed.envHttpHeaders ?? parsed.env_http_headers) ?? {} + : undefined, }; }; export const createMcpPayloadFromForm = ( provider: McpProvider, formData: McpFormState, + options?: CreateMcpPayloadOptions, ): UpsertProviderMcpServerPayload => { if (formData.importMode === 'json') { - return parseJsonMcpPayload(provider, formData); + return parseJsonMcpPayload(provider, formData, options); } + assertSupportedTransport(provider, formData.transport, options); + + const supportsWorkingDirectory = options?.supportsWorkingDirectory ?? MCP_SUPPORTS_WORKING_DIRECTORY[provider]; + const includeProviderSpecificFields = options?.includeProviderSpecificFields ?? provider === 'codex'; + return { name: formData.name.trim(), scope: formData.scope, @@ -139,11 +174,11 @@ export const createMcpPayloadFromForm = ( command: formData.transport === 'stdio' ? formData.command.trim() : undefined, args: formData.transport === 'stdio' ? formData.args : undefined, env: formData.env, - cwd: MCP_SUPPORTS_WORKING_DIRECTORY[provider] ? formData.cwd.trim() || undefined : undefined, + cwd: supportsWorkingDirectory ? formData.cwd.trim() || undefined : undefined, url: formData.transport !== 'stdio' ? formData.url.trim() : undefined, headers: formData.transport !== 'stdio' ? formData.headers : undefined, - envVars: provider === 'codex' ? formData.envVars : undefined, - bearerTokenEnvVar: provider === 'codex' ? formData.bearerTokenEnvVar.trim() || undefined : undefined, - envHttpHeaders: provider === 'codex' ? formData.envHttpHeaders : undefined, + envVars: includeProviderSpecificFields ? formData.envVars : undefined, + bearerTokenEnvVar: includeProviderSpecificFields ? formData.bearerTokenEnvVar.trim() || undefined : undefined, + envHttpHeaders: includeProviderSpecificFields ? formData.envHttpHeaders : undefined, }; }; diff --git a/src/components/mcp/view/McpServers.tsx b/src/components/mcp/view/McpServers.tsx index 393d7ea5..8ec9d03e 100644 --- a/src/components/mcp/view/McpServers.tsx +++ b/src/components/mcp/view/McpServers.tsx @@ -4,7 +4,12 @@ import { useTranslation } from 'react-i18next'; import type { McpProject, McpProvider, McpScope, ProviderMcpServer } from '../types'; import { IS_PLATFORM } from '../../../constants/config'; import { Badge, Button } from '../../../shared/view/ui'; -import { MCP_PROVIDER_BUTTON_CLASSES, MCP_PROVIDER_NAMES } from '../constants'; +import { + MCP_GLOBAL_SUPPORTED_SCOPES, + MCP_GLOBAL_SUPPORTED_TRANSPORTS, + MCP_PROVIDER_BUTTON_CLASSES, + MCP_PROVIDER_NAMES, +} from '../constants'; import { useMcpServers } from '../hooks/useMcpServers'; import { maskSecret } from '../utils/mcpFormatting'; @@ -100,10 +105,14 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer deleteError, saveStatus, isFormOpen, + isGlobalFormOpen, editingServer, openForm, + openGlobalForm, closeForm, + closeGlobalForm, submitForm, + submitGlobalForm, deleteServer, } = useMcpServers({ selectedProvider, currentProjects }); @@ -111,6 +120,12 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer const description = t(`mcpServers.description.${selectedProvider}`, { defaultValue: `Model Context Protocol servers provide additional tools and data sources to ${providerName}`, }); + const globalButtonLabel = 'Add Global MCP Server'; + const providerButtonLabel = `Add ${providerName} MCP Server`; + const globalAddDescription = 'Add Global MCP Server writes one common stdio or HTTP server to Claude, Cursor, Codex, and Gemini.'; + const providerAddDescription = `${providerButtonLabel} only changes ${providerName}.`; + const globalModalDescription = 'Adds this MCP server to every provider: Claude, Cursor, Codex, and Gemini. ' + + 'Only stdio and HTTP transports are supported because the same config must work across all providers.'; return (
@@ -120,17 +135,35 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer

{description}

-
- - {saveStatus === 'success' && ( - {t('saveStatus.success')} - )} - {isLoadingProjectScopes && ( - Refreshing project scopes... - )} +
+
+ + +
+
+ {saveStatus === 'success' && ( + {t('saveStatus.success')} + )} + {isLoadingProjectScopes && ( + Refreshing project scopes... + )} +
{(loadError || deleteError) && ( @@ -223,9 +256,26 @@ export default function McpServers({ selectedProvider, currentProjects }: McpSer isOpen={isFormOpen} editingServer={editingServer} currentProjects={currentProjects} + title={editingServer ? undefined : providerButtonLabel} + submitLabel={providerButtonLabel} onClose={closeForm} onSubmit={submitForm} /> + + submitGlobalForm(formData)} + />
); } diff --git a/src/components/mcp/view/modals/McpServerFormModal.tsx b/src/components/mcp/view/modals/McpServerFormModal.tsx index 09f0c217..afffa512 100644 --- a/src/components/mcp/view/modals/McpServerFormModal.tsx +++ b/src/components/mcp/view/modals/McpServerFormModal.tsx @@ -9,50 +9,77 @@ import { MCP_SUPPORTS_WORKING_DIRECTORY, } from '../../constants'; import { useMcpServerForm } from '../../hooks/useMcpServerForm'; -import type { McpFormState, McpProject, McpProvider, McpScope, ProviderMcpServer } from '../../types'; +import type { + McpFormMode, + McpFormState, + McpProject, + McpProvider, + McpScope, + McpTransport, + ProviderMcpServer, +} from '../../types'; type McpServerFormModalProps = { provider: McpProvider; + mode?: McpFormMode; isOpen: boolean; editingServer: ProviderMcpServer | null; currentProjects: McpProject[]; + title?: string; + description?: string; + submitLabel?: string; + supportedScopes?: McpScope[]; + supportedTransports?: McpTransport[]; onClose: () => void; onSubmit: (formData: McpFormState, editingServer: ProviderMcpServer | null) => Promise; }; -const getScopeLabel = (scope: McpScope): string => { +const getScopeLabel = (scope: McpScope, mode: McpFormMode): string => { if (scope === 'user') { - return 'User (Global)'; + return mode === 'global' ? 'User (All Providers)' : 'User (Global)'; } if (scope === 'local') { return 'Claude Local'; } - return 'Project'; + return mode === 'global' ? 'Project (All Providers)' : 'Project'; }; -const getScopeDescription = (scope: McpScope): string => { +const getScopeDescription = (scope: McpScope, mode: McpFormMode): string => { if (scope === 'user') { - return 'Available across all projects on your machine'; + return mode === 'global' + ? 'Writes to each provider user config and is available across projects on this machine' + : 'Available across all projects on your machine'; } if (scope === 'local') { return 'Stored in Claude user settings for the selected project'; } - return 'Stored in the selected project workspace'; + return mode === 'global' + ? 'Writes to the selected project workspace for every provider' + : 'Stored in the selected project workspace'; }; export default function McpServerFormModal({ provider, + mode = 'provider', isOpen, editingServer, currentProjects, + title, + description, + submitLabel, + supportedScopes, + supportedTransports, onClose, onSubmit, }: McpServerFormModalProps) { const { t } = useTranslation('settings'); + const isGlobalMode = mode === 'global'; + const availableScopes = supportedScopes ?? MCP_SUPPORTED_SCOPES[provider]; + const availableTransports = supportedTransports ?? MCP_SUPPORTED_TRANSPORTS[provider]; const { formData, multilineText, @@ -72,6 +99,11 @@ export default function McpServerFormModal({ isOpen, editingServer, currentProjects, + supportedScopes: availableScopes, + supportedTransports: availableTransports, + unsupportedTransportMessage: isGlobalMode + ? (transport) => `Add MCP Server supports only stdio and http across all providers, not ${transport}.` + : undefined, onSubmit, }); @@ -80,23 +112,30 @@ export default function McpServerFormModal({ } const providerName = MCP_PROVIDER_NAMES[provider]; + const modalTitle = title ?? (isEditing ? t('mcpForm.title.edit') : t('mcpForm.title.add')); + const addButtonLabel = submitLabel ?? `${t('mcpForm.actions.addServer')} to ${providerName}`; const showProjectSelector = formData.scope !== 'user'; const supportsHttpHeaders = formData.transport === 'http' || formData.transport === 'sse'; - const supportsWorkingDirectory = MCP_SUPPORTS_WORKING_DIRECTORY[provider]; + const supportsWorkingDirectory = !isGlobalMode && MCP_SUPPORTS_WORKING_DIRECTORY[provider]; + const showCodexOnlyFields = provider === 'codex' && !isGlobalMode; return (
-

- {isEditing ? t('mcpForm.title.edit') : t('mcpForm.title.add')} -

+

{modalTitle}

+ {description && ( +
+ {description} +
+ )} + {!isEditing && (
))}
-

{getScopeDescription(formData.scope)}

+

{getScopeDescription(formData.scope, mode)}

{showProjectSelector && ( @@ -219,7 +258,7 @@ export default function McpServerFormModal({ onChange={(event) => updateTransport(event.target.value as McpFormState['transport'])} className="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" > - {MCP_SUPPORTED_TRANSPORTS[provider].map((transport) => ( + {availableTransports.map((transport) => ( @@ -344,7 +383,7 @@ export default function McpServerFormModal({
)} - {provider === 'codex' && formData.importMode === 'form' && formData.transport === 'stdio' && ( + {showCodexOnlyFields && formData.importMode === 'form' && formData.transport === 'stdio' && (
)} - {provider === 'codex' && formData.importMode === 'form' && formData.transport === 'http' && ( + {showCodexOnlyFields && formData.importMode === 'form' && formData.transport === 'http' && (