@@ -475,4 +475,258 @@ describe('mcp list command', () => {
475475 ) ;
476476 expect ( mockedCreateTransport ) . not . toHaveBeenCalled ( ) ;
477477 } ) ;
478+
479+ it ( 'should block servers excluded by user settings even if workspace settings override/clear the excluded list' , async ( ) => {
480+ const mockSettings = createMockSettings ( {
481+ user : {
482+ path : '/user/settings.json' ,
483+ settings : {
484+ mcp : {
485+ excluded : [ 'blocked-server' ] ,
486+ } ,
487+ } ,
488+ originalSettings : {
489+ mcp : {
490+ excluded : [ 'blocked-server' ] ,
491+ } ,
492+ } ,
493+ } ,
494+ workspace : {
495+ path : '/workspace/settings.json' ,
496+ settings : {
497+ mcp : {
498+ excluded : [ ] ,
499+ } ,
500+ } ,
501+ originalSettings : {
502+ mcp : {
503+ excluded : [ ] ,
504+ } ,
505+ } ,
506+ } ,
507+ mcpServers : {
508+ 'blocked-server' : { command : '/test/server' } ,
509+ } ,
510+ isTrusted : true ,
511+ merged : {
512+ mcp : {
513+ excluded : [ ] , // workspace has overridden user settings!
514+ } ,
515+ mcpServers : {
516+ 'blocked-server' : { command : '/test/server' } ,
517+ } ,
518+ } ,
519+ } ) ;
520+
521+ mockedLoadSettings . mockReturnValue ( mockSettings ) ;
522+
523+ await listMcpServers ( ) ;
524+
525+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
526+ expect . stringContaining (
527+ 'blocked-server: /test/server (stdio) - Blocked' ,
528+ ) ,
529+ ) ;
530+ expect ( mockedCreateTransport ) . not . toHaveBeenCalled ( ) ;
531+ } ) ;
532+
533+ it ( 'should block servers case-insensitively when excluded' , async ( ) => {
534+ const mockSettings = createMockSettings ( {
535+ user : {
536+ path : '/user/settings.json' ,
537+ settings : {
538+ mcp : {
539+ excluded : [ 'BLOCKED-server' ] ,
540+ } ,
541+ } ,
542+ originalSettings : {
543+ mcp : {
544+ excluded : [ 'BLOCKED-server' ] ,
545+ } ,
546+ } ,
547+ } ,
548+ mcpServers : {
549+ 'blocked-server' : { command : '/test/server' } ,
550+ } ,
551+ isTrusted : true ,
552+ merged : {
553+ mcpServers : {
554+ 'blocked-server' : { command : '/test/server' } ,
555+ } ,
556+ } ,
557+ } ) ;
558+
559+ mockedLoadSettings . mockReturnValue ( mockSettings ) ;
560+
561+ await listMcpServers ( ) ;
562+
563+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
564+ expect . stringContaining (
565+ 'blocked-server: /test/server (stdio) - Blocked' ,
566+ ) ,
567+ ) ;
568+ expect ( mockedCreateTransport ) . not . toHaveBeenCalled ( ) ;
569+ } ) ;
570+
571+ it ( 'should restrict allowed servers to the intersection of all defined allowlists' , async ( ) => {
572+ const mockSettings = createMockSettings ( {
573+ user : {
574+ path : '/user/settings.json' ,
575+ settings : {
576+ mcp : {
577+ allowed : [ 'allowed-server-1' , 'allowed-server-2' ] ,
578+ } ,
579+ } ,
580+ originalSettings : {
581+ mcp : {
582+ allowed : [ 'allowed-server-1' , 'allowed-server-2' ] ,
583+ } ,
584+ } ,
585+ } ,
586+ workspace : {
587+ path : '/workspace/settings.json' ,
588+ settings : {
589+ mcp : {
590+ allowed : [ 'allowed-server-1' , 'malicious-server' ] ,
591+ } ,
592+ } ,
593+ originalSettings : {
594+ mcp : {
595+ allowed : [ 'allowed-server-1' , 'malicious-server' ] ,
596+ } ,
597+ } ,
598+ } ,
599+ mcpServers : {
600+ 'allowed-server-1' : { command : '/allowed/1' } ,
601+ 'allowed-server-2' : { command : '/allowed/2' } ,
602+ 'malicious-server' : { command : '/malicious' } ,
603+ } ,
604+ isTrusted : true ,
605+ merged : {
606+ mcp : {
607+ allowed : [ 'allowed-server-1' , 'malicious-server' ] , // workspace overrode user settings!
608+ } ,
609+ mcpServers : {
610+ 'allowed-server-1' : { command : '/allowed/1' } ,
611+ 'allowed-server-2' : { command : '/allowed/2' } ,
612+ 'malicious-server' : { command : '/malicious' } ,
613+ } ,
614+ } ,
615+ } ) ;
616+
617+ mockedLoadSettings . mockReturnValue ( mockSettings ) ;
618+ mockClient . connect . mockResolvedValue ( undefined ) ;
619+ mockClient . ping . mockResolvedValue ( undefined ) ;
620+
621+ await listMcpServers ( ) ;
622+
623+ // allowed-server-1 is in the intersection, so it should connect
624+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
625+ expect . stringContaining (
626+ 'allowed-server-1: /allowed/1 (stdio) - Connected' ,
627+ ) ,
628+ ) ;
629+ // allowed-server-2 and malicious-server are not in the intersection, so they should be Blocked
630+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
631+ expect . stringContaining (
632+ 'allowed-server-2: /allowed/2 (stdio) - Blocked' ,
633+ ) ,
634+ ) ;
635+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
636+ expect . stringContaining (
637+ 'malicious-server: /malicious (stdio) - Blocked' ,
638+ ) ,
639+ ) ;
640+
641+ expect ( mockedCreateTransport ) . toHaveBeenCalledTimes ( 1 ) ;
642+ expect ( mockedCreateTransport ) . toHaveBeenCalledWith (
643+ 'allowed-server-1' ,
644+ expect . any ( Object ) ,
645+ false ,
646+ expect . any ( Object ) ,
647+ ) ;
648+ } ) ;
649+
650+ it ( 'should block all servers if the intersection of user and workspace allowlists is empty (disjoint allowlists)' , async ( ) => {
651+ const mockSettings = createMockSettings ( {
652+ user : {
653+ path : '/user/settings.json' ,
654+ settings : {
655+ mcp : {
656+ allowed : [ 'user-allowed-server' ] ,
657+ } ,
658+ } ,
659+ originalSettings : {
660+ mcp : {
661+ allowed : [ 'user-allowed-server' ] ,
662+ } ,
663+ } ,
664+ } ,
665+ workspace : {
666+ path : '/workspace/settings.json' ,
667+ settings : {
668+ mcp : {
669+ allowed : [ 'workspace-allowed-server' ] ,
670+ } ,
671+ } ,
672+ originalSettings : {
673+ mcp : {
674+ allowed : [ 'workspace-allowed-server' ] ,
675+ } ,
676+ } ,
677+ } ,
678+ mcpServers : {
679+ 'user-allowed-server' : { command : '/allowed/user' } ,
680+ 'workspace-allowed-server' : { command : '/allowed/workspace' } ,
681+ } ,
682+ isTrusted : true ,
683+ merged : {
684+ mcp : {
685+ allowed : [ 'workspace-allowed-server' ] , // workspace override
686+ } ,
687+ mcpServers : {
688+ 'user-allowed-server' : { command : '/allowed/user' } ,
689+ 'workspace-allowed-server' : { command : '/allowed/workspace' } ,
690+ } ,
691+ } ,
692+ } ) ;
693+
694+ mockedLoadSettings . mockReturnValue ( mockSettings ) ;
695+
696+ await listMcpServers ( ) ;
697+
698+ // Since the intersection is empty ([]), both servers should be Blocked!
699+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
700+ expect . stringContaining (
701+ 'user-allowed-server: /allowed/user (stdio) - Blocked' ,
702+ ) ,
703+ ) ;
704+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
705+ expect . stringContaining (
706+ 'workspace-allowed-server: /allowed/workspace (stdio) - Blocked' ,
707+ ) ,
708+ ) ;
709+ expect ( mockedCreateTransport ) . not . toHaveBeenCalled ( ) ;
710+ } ) ;
711+
712+ it ( 'should block all servers if allowlist is configured as empty array []' , async ( ) => {
713+ const mockSettings = createMockSettings ( {
714+ mcp : {
715+ allowed : [ ] , // empty allowlist configured!
716+ } ,
717+ mcpServers : {
718+ 'test-server' : { command : '/test/server' } ,
719+ } ,
720+ isTrusted : true ,
721+ } ) ;
722+
723+ mockedLoadSettings . mockReturnValue ( mockSettings ) ;
724+
725+ await listMcpServers ( ) ;
726+
727+ expect ( debugLogger . log ) . toHaveBeenCalledWith (
728+ expect . stringContaining ( 'test-server: /test/server (stdio) - Blocked' ) ,
729+ ) ;
730+ expect ( mockedCreateTransport ) . not . toHaveBeenCalled ( ) ;
731+ } ) ;
478732} ) ;
0 commit comments