@@ -106,6 +106,21 @@ interface McpServer {
106106
107107const logger = createLogger ( 'McpSettings' )
108108
109+ /**
110+ * Checks if a URL's hostname is in the allowed domains list.
111+ * Returns true if no allowlist is configured (null) or the domain matches.
112+ */
113+ function isDomainAllowed ( url : string | undefined , allowedDomains : string [ ] | null ) : boolean {
114+ if ( allowedDomains === null ) return true
115+ if ( ! url ) return true
116+ try {
117+ const hostname = new URL ( url ) . hostname . toLowerCase ( )
118+ return allowedDomains . includes ( hostname )
119+ } catch {
120+ return true
121+ }
122+ }
123+
109124const DEFAULT_FORM_DATA : McpServerFormData = {
110125 name : '' ,
111126 transport : 'streamable-http' ,
@@ -390,6 +405,15 @@ export function MCP({ initialServerId }: MCPProps) {
390405 } = useMcpServerTest ( )
391406 const availableEnvVars = useAvailableEnvVarKeys ( workspaceId )
392407
408+ const [ allowedMcpDomains , setAllowedMcpDomains ] = useState < string [ ] | null > ( null )
409+
410+ useEffect ( ( ) => {
411+ fetch ( '/api/settings/allowed-mcp-domains' )
412+ . then ( ( res ) => res . json ( ) )
413+ . then ( ( data ) => setAllowedMcpDomains ( data . allowedMcpDomains ?? null ) )
414+ . catch ( ( ) => setAllowedMcpDomains ( null ) )
415+ } , [ ] )
416+
393417 const urlInputRef = useRef < HTMLInputElement > ( null )
394418
395419 const [ showAddForm , setShowAddForm ] = useState ( false )
@@ -1006,10 +1030,12 @@ export function MCP({ initialServerId }: MCPProps) {
10061030 const showNoResults = searchTerm . trim ( ) && filteredServers . length === 0 && servers . length > 0
10071031
10081032 const isFormValid = formData . name . trim ( ) && formData . url ?. trim ( )
1009- const isSubmitDisabled = serversLoading || isAddingServer || ! isFormValid
1033+ const isAddDomainBlocked = ! isDomainAllowed ( formData . url , allowedMcpDomains )
1034+ const isSubmitDisabled = serversLoading || isAddingServer || ! isFormValid || isAddDomainBlocked
10101035 const testButtonLabel = getTestButtonLabel ( testResult , isTestingConnection )
10111036
10121037 const isEditFormValid = editFormData . name . trim ( ) && editFormData . url ?. trim ( )
1038+ const isEditDomainBlocked = ! isDomainAllowed ( editFormData . url , allowedMcpDomains )
10131039 const editTestButtonLabel = getTestButtonLabel ( editTestResult , isEditTestingConnection )
10141040 const hasEditChanges = useMemo ( ( ) => {
10151041 if ( editFormData . name !== editOriginalData . name ) return true
@@ -1299,6 +1325,11 @@ export function MCP({ initialServerId }: MCPProps) {
12991325 onChange = { ( e ) => handleEditInputChange ( 'url' , e . target . value ) }
13001326 onScroll = { setEditUrlScrollLeft }
13011327 />
1328+ { isEditDomainBlocked && (
1329+ < p className = 'mt-[4px] text-[12px] text-[var(--text-error)]' >
1330+ Domain not permitted by server policy
1331+ </ p >
1332+ ) }
13021333 </ FormField >
13031334
13041335 < div className = 'flex flex-col gap-[8px]' >
@@ -1351,7 +1382,7 @@ export function MCP({ initialServerId }: MCPProps) {
13511382 < Button
13521383 variant = 'default'
13531384 onClick = { handleEditTestConnection }
1354- disabled = { isEditTestingConnection || ! isEditFormValid }
1385+ disabled = { isEditTestingConnection || ! isEditFormValid || isEditDomainBlocked }
13551386 >
13561387 { editTestButtonLabel }
13571388 </ Button >
@@ -1361,7 +1392,9 @@ export function MCP({ initialServerId }: MCPProps) {
13611392 </ Button >
13621393 < Button
13631394 onClick = { handleSaveEdit }
1364- disabled = { ! hasEditChanges || isUpdatingServer || ! isEditFormValid }
1395+ disabled = {
1396+ ! hasEditChanges || isUpdatingServer || ! isEditFormValid || isEditDomainBlocked
1397+ }
13651398 variant = 'tertiary'
13661399 >
13671400 { isUpdatingServer ? 'Saving...' : 'Save' }
@@ -1434,6 +1467,11 @@ export function MCP({ initialServerId }: MCPProps) {
14341467 onChange = { ( e ) => handleInputChange ( 'url' , e . target . value ) }
14351468 onScroll = { ( scrollLeft ) => handleUrlScroll ( scrollLeft ) }
14361469 />
1470+ { isAddDomainBlocked && (
1471+ < p className = 'mt-[4px] text-[12px] text-[var(--text-error)]' >
1472+ Domain not permitted by server policy
1473+ </ p >
1474+ ) }
14371475 </ FormField >
14381476
14391477 < div className = 'flex flex-col gap-[8px]' >
@@ -1479,7 +1517,7 @@ export function MCP({ initialServerId }: MCPProps) {
14791517 < Button
14801518 variant = 'default'
14811519 onClick = { handleTestConnection }
1482- disabled = { isTestingConnection || ! isFormValid }
1520+ disabled = { isTestingConnection || ! isFormValid || isAddDomainBlocked }
14831521 >
14841522 { testButtonLabel }
14851523 </ Button >
@@ -1489,7 +1527,9 @@ export function MCP({ initialServerId }: MCPProps) {
14891527 Cancel
14901528 </ Button >
14911529 < Button onClick = { handleAddServer } disabled = { isSubmitDisabled } variant = 'tertiary' >
1492- { isSubmitDisabled && isFormValid ? 'Adding...' : 'Add Server' }
1530+ { isSubmitDisabled && isFormValid && ! isAddDomainBlocked
1531+ ? 'Adding...'
1532+ : 'Add Server' }
14931533 </ Button >
14941534 </ div >
14951535 </ div >
0 commit comments