11import { zodResolver } from "@hookform/resolvers/zod" ;
2+ import { ensureBuiltinServerIds , getBuiltinServerIds } from "@shared/utils/mcp-utils" ;
23import { ChevronLeft , ChevronRight , Copy , Trash2 } from "lucide-react" ;
34import { useEffect , useMemo , useRef , useState } from "react" ;
45import { useForm } from "react-hook-form" ;
@@ -104,45 +105,79 @@ export function PromptForm({
104105 } ) ;
105106 } , [ mcpServers . length ] ) ;
106107
107- // Auto-select all servers for existing prompts without selectedMcpServerIds
108- // This runs once after servers are loaded
108+ // Initialise MCP server selection once after servers are loaded. Three cases:
109+ // default prompt → select all servers (locked, not editable)
110+ // new prompt → pre-select built-in server IDs only
111+ // existing prompt → keep saved selection and ensure built-ins are always included;
112+ // if no prior selection exists, default to all non-builtin servers
109113 // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally omitting form methods to prevent re-runs
110114 useEffect ( ( ) => {
111- if ( serversLoaded && ! isNewPrompt && ! hasAutoSelectedServers . current && mcpServers . length > 0 ) {
112- const currentSelection = form . getValues ( "selectedMcpServerIds" ) ;
113- if ( ! currentSelection || currentSelection . length === 0 ) {
115+ if ( serversLoaded && ! hasAutoSelectedServers . current && mcpServers . length > 0 ) {
116+ const builtinIds = getBuiltinServerIds ( mcpServers ) ;
117+
118+ if ( isDefault ) {
114119 const allServerIds = mcpServers . map ( ( s ) => s . id ) . filter ( ( id ) : id is string => ! ! id ) ;
115120 form . setValue ( "selectedMcpServerIds" , allServerIds , { shouldDirty : false } ) ;
121+ } else if ( isNewPrompt ) {
122+ form . setValue ( "selectedMcpServerIds" , builtinIds , { shouldDirty : false } ) ;
123+ } else {
124+ const currentSelection = form . getValues ( "selectedMcpServerIds" ) ;
125+ if ( ! currentSelection || currentSelection . length === 0 ) {
126+ // No prior selection: default to all non-builtin servers (regardless of connection status)
127+ const allNonBuiltinIds = mcpServers
128+ . filter ( ( s ) => ! s . builtin )
129+ . map ( ( s ) => s . id )
130+ . filter ( ( id ) : id is string => ! ! id ) ;
131+ form . setValue (
132+ "selectedMcpServerIds" ,
133+ ensureBuiltinServerIds ( allNonBuiltinIds , mcpServers ) ,
134+ { shouldDirty : false } ,
135+ ) ;
136+ } else {
137+ // Existing selection: preserve it and silently add any missing built-ins
138+ form . setValue (
139+ "selectedMcpServerIds" ,
140+ ensureBuiltinServerIds ( currentSelection , mcpServers ) ,
141+ { shouldDirty : false } ,
142+ ) ;
143+ }
116144 }
117145 hasAutoSelectedServers . current = true ;
118146 }
119147 // eslint-disable-next-line react-hooks/exhaustive-deps
120- } , [ serversLoaded , isNewPrompt , mcpServers ] ) ;
148+ } , [ serversLoaded , isDefault , isNewPrompt , mcpServers ] ) ;
121149
122150 const handleSubmit = async ( andActivate : boolean ) => {
123151 const isValid = await form . trigger ( ) ;
124152 if ( ! isValid ) return ;
125153
126154 const data = form . getValues ( ) ;
127155
128- // Validate that at least one enabled non-built-in MCP server is selected (if any are available)
129- // Skip validation for default prompts since their server selection cannot be changed
130156 if ( ! isDefault ) {
131- const availableEnabledNonBuiltinServers = mcpServers . filter (
132- ( server ) => ! server . builtin && server . enabled !== false ,
157+ const nonBuiltinServers = mcpServers . filter ( ( s ) => ! s . builtin ) ;
158+
159+ // Case 1: no non-builtin servers exist at all → hard block
160+ if ( nonBuiltinServers . length === 0 ) {
161+ form . setError ( "selectedMcpServerIds" , {
162+ type : "manual" ,
163+ message : "No MCP servers configured. Please add a server in the MCP settings tab." ,
164+ } ) ;
165+ return ;
166+ }
167+
168+ // Case 2: non-builtin servers exist but none are selected → hard block
169+ const selectedNonBuiltinIds = ( data . selectedMcpServerIds ?? [ ] ) . filter ( ( id ) =>
170+ nonBuiltinServers . some ( ( s ) => s . id === id ) ,
133171 ) ;
134- if ( availableEnabledNonBuiltinServers . length > 0 ) {
135- const selectedEnabledNonBuiltinServers = availableEnabledNonBuiltinServers . filter (
136- ( server ) => server . id && data . selectedMcpServerIds ?. includes ( server . id ) ,
137- ) ;
138- if ( selectedEnabledNonBuiltinServers . length === 0 ) {
139- form . setError ( "selectedMcpServerIds" , {
140- type : "manual" ,
141- message : "Please select at least one enabled MCP server (excluding built-in servers)" ,
142- } ) ;
143- return ;
144- }
172+ if ( selectedNonBuiltinIds . length === 0 ) {
173+ form . setError ( "selectedMcpServerIds" , {
174+ type : "manual" ,
175+ message : "Please select at least one MCP server (excluding built-in servers)." ,
176+ } ) ;
177+ return ;
145178 }
179+
180+ // Case 3: all selected non-builtins are disconnected → soft warning, allow save (handled in UI)
146181 }
147182
148183 await onSubmit ( data , andActivate ) ;
@@ -201,7 +236,7 @@ export function PromptForm({
201236 render = { ( { field } ) => (
202237 < FormItem className = "flex flex-col flex-1 min-h-0 overflow-hidden shrink-0 max-w-full" >
203238 < div className = "flex items-center justify-between" >
204- < FormLabel className = "text-white/90 text-sm" > Prompt Instructions</ FormLabel >
239+ < FormLabel className = "text-white/90 text-sm" > Prompt Instructions * </ FormLabel >
205240 < Button
206241 type = "button"
207242 variant = "ghost"
@@ -240,6 +275,12 @@ export function PromptForm({
240275 . filter ( ( s ) => field . value ?. includes ( s . id ) )
241276 . map ( ( s ) => s . name ) ;
242277
278+ const hasDisconnectedSelection =
279+ ! isDefault &&
280+ serversWithIds . some (
281+ ( s ) => ! s . builtin && s . enabled === false && field . value ?. includes ( s . id ) ,
282+ ) ;
283+
243284 return (
244285 < FormItem className = "shrink-0" >
245286 < FormLabel > MCP Servers *</ FormLabel >
@@ -262,39 +303,40 @@ export function PromptForm({
262303 aria-live = "polite"
263304 >
264305 { paginatedServers . map ( ( server ) => {
265- const isChecked = field . value ?. includes ( server . id ) ?? false ;
266- const isDisabled = isDefault || server . enabled === false ;
306+ const isBuiltin = server . builtin ?? false ;
307+ const isServerDisabled = server . enabled === false ;
308+ const isChecked =
309+ isDefault || isBuiltin || ( field . value ?. includes ( server . id ) ?? false ) ;
310+ // Only lock for default prompts and built-ins; disabled servers remain toggleable
311+ const isCheckboxDisabled = isDefault || isBuiltin ;
267312 const handleToggle = ( ) => {
268313 const newValue = isChecked
269314 ? ( field . value || [ ] ) . filter ( ( id ) => id !== server . id )
270315 : [ ...( field . value || [ ] ) , server . id ] ;
271316 field . onChange ( newValue ) ;
272317 } ;
273318 return (
274- < div
275- key = { server . id }
276- className = { `flex items-center gap-3 p-1 rounded ${
277- isDisabled ? "opacity-50" : ""
278- } `}
279- >
319+ < div key = { server . id } className = "flex items-center gap-3 p-1 rounded" >
280320 < Checkbox
281321 id = { `server-${ server . id } ` }
282322 checked = { isChecked }
283323 onCheckedChange = { handleToggle }
284- disabled = { isDisabled }
324+ disabled = { isCheckboxDisabled }
285325 />
286326 < label
287327 htmlFor = { `server-${ server . id } ` }
288328 className = { `text-sm flex-1 select-none ${
289- isDisabled ? "cursor-not-allowed" : "cursor-pointer"
329+ isCheckboxDisabled ? "cursor-not-allowed" : "cursor-pointer"
290330 } `}
291331 >
292332 { server . name }
293- { server . builtin && (
333+ { isBuiltin && (
294334 < span className = "ml-2 text-xs text-white/50" > (Built-in)</ span >
295335 ) }
296- { isDisabled && (
297- < span className = "ml-2 text-xs text-yellow-500/70" > (Disabled)</ span >
336+ { isServerDisabled && (
337+ < span className = "ml-2 text-xs text-yellow-500/70" >
338+ (Disconnected)
339+ </ span >
298340 ) }
299341 </ label >
300342 </ div >
@@ -332,16 +374,23 @@ export function PromptForm({
332374 </ div >
333375 ) }
334376 </ div >
377+ { hasDisconnectedSelection && (
378+ < p className = "text-xs text-yellow-500/80 mt-1" >
379+ Some selected servers are disconnected. Connect them in MCP settings tab to
380+ make their tools available.
381+ </ p >
382+ ) }
335383 < FormMessage />
336384 </ FormItem >
337385 ) ;
338386 } }
339387 />
340388 ) }
341389
342- { serversLoaded && mcpServers . length === 0 && (
390+ { serversLoaded && mcpServers . every ( ( s ) => s . builtin ) && (
343391 < div className = "text-sm text-yellow-500/80 p-3 rounded-md border border-yellow-500/30 bg-yellow-500/10" >
344- No MCP servers configured. Please add MCP servers in the MCP settings tab.
392+ No external MCP servers configured. You can add additional MCP servers in the MCP
393+ settings tab.
345394 </ div >
346395 ) }
347396
0 commit comments