@@ -111,18 +111,6 @@ export function McpRestrictionsEditor({
111111 }
112112 }
113113
114- // Helper to check if server has complex restrictions (more than just allow/disallow)
115- const _hasComplexRestrictions = ( server : McpServer ) => {
116- // Check if server has tool-level restrictions
117- const hasToolRestrictions =
118- allowedTools . some ( ( t ) => t . serverName === server . name ) ||
119- disallowedTools . some ( ( t ) => t . serverName === server . name )
120-
121- // Only consider it "restricted" if it has tool-level restrictions
122- // Simple allow/disallow at server level doesn't count as "restricted"
123- return hasToolRestrictions
124- }
125-
126114 // Group servers by their status
127115 const serverGroups = {
128116 enabled : availableServers . filter ( ( server ) => {
@@ -582,6 +570,60 @@ function CompactServerRow({
582570 return hasToolRestrictions
583571 }
584572
573+ // Helper function to resolve tool access using pattern specificity
574+ const resolveToolAccess = ( toolName : string , serverName : string ) : boolean => {
575+ const serverAllowedTools = allowedTools . filter ( ( t ) => t . serverName === serverName )
576+ const serverDisallowedTools = disallowedTools . filter ( ( t ) => t . serverName === serverName )
577+
578+ // If no restrictions for this server, allow the tool
579+ if ( serverAllowedTools . length === 0 && serverDisallowedTools . length === 0 ) {
580+ return true
581+ }
582+
583+ // Collect all matching patterns from both allow and block lists
584+ const matchingAllowPatterns : string [ ] = [ ]
585+ const matchingBlockPatterns : string [ ] = [ ]
586+
587+ // Check allow patterns
588+ for ( const pattern of serverAllowedTools ) {
589+ if ( patternMatching . matchesGlobPattern ( toolName , pattern . toolName ) ) {
590+ matchingAllowPatterns . push ( pattern . toolName )
591+ }
592+ }
593+
594+ // Check block patterns
595+ for ( const pattern of serverDisallowedTools ) {
596+ if ( patternMatching . matchesGlobPattern ( toolName , pattern . toolName ) ) {
597+ matchingBlockPatterns . push ( pattern . toolName )
598+ }
599+ }
600+
601+ // If no patterns match, use default behavior
602+ if ( matchingAllowPatterns . length === 0 && matchingBlockPatterns . length === 0 ) {
603+ // If only allowedTools is defined, tool must match at least one entry (none did, so block)
604+ if ( serverAllowedTools . length > 0 && serverDisallowedTools . length === 0 ) {
605+ return false
606+ }
607+ // If only disallowedTools is defined, tool is allowed (none matched)
608+ if ( serverAllowedTools . length === 0 && serverDisallowedTools . length > 0 ) {
609+ return true
610+ }
611+ // If both are defined but none matched, allow by default
612+ return true
613+ }
614+
615+ // Find the most specific pattern from all matching patterns
616+ const allMatchingPatterns = [ ...matchingAllowPatterns , ...matchingBlockPatterns ]
617+ const mostSpecificPattern = patternMatching . findMostSpecificMatchingPattern ( toolName , allMatchingPatterns )
618+
619+ if ( ! mostSpecificPattern ) {
620+ return true // Default to allow if no pattern found (shouldn't happen)
621+ }
622+
623+ // Determine if the most specific pattern is from allow or block list
624+ return matchingAllowPatterns . includes ( mostSpecificPattern )
625+ }
626+
585627 // Handler for individual tool actions (allow/block)
586628 const handleToolAction = ( toolName : string , action : "allow" | "block" ) => {
587629 if ( action === "block" ) {
@@ -681,30 +723,8 @@ function CompactServerRow({
681723 return server . tools . length
682724 }
683725
684- // For servers with restrictions, calculate how many tools are actually enabled
685- const serverAllowedTools = allowedTools . filter ( ( t ) => t . serverName === server . name )
686- const serverDisallowedTools = disallowedTools . filter ( ( t ) => t . serverName === server . name )
687-
688- // If there are allowed tools specified for this server, only those are enabled
689- if ( serverAllowedTools . length > 0 ) {
690- return server . tools . filter ( ( tool ) => {
691- return serverAllowedTools . some ( ( allowedTool ) =>
692- patternMatching . matchesPattern ( tool . name , allowedTool . toolName ) ,
693- )
694- } ) . length
695- }
696-
697- // If no allowed tools but there are disallowed tools, count enabled tools
698- if ( serverDisallowedTools . length > 0 ) {
699- return server . tools . filter ( ( tool ) => {
700- return ! serverDisallowedTools . some ( ( disallowedTool ) =>
701- patternMatching . matchesPattern ( tool . name , disallowedTool . toolName ) ,
702- )
703- } ) . length
704- }
705-
706- // No tool restrictions for this server
707- return server . tools . length
726+ // For servers with restrictions, calculate how many tools are actually enabled using pattern specificity
727+ return server . tools . filter ( ( tool ) => resolveToolAccess ( tool . name , server . name ) ) . length
708728 }
709729
710730 const enabledToolsCount = getEnabledToolsCount ( )
@@ -720,26 +740,11 @@ function CompactServerRow({
720740 }
721741 }
722742
723- const serverAllowedTools = allowedTools . filter ( ( t ) => t . serverName === server . name )
724- const serverDisallowedTools = disallowedTools . filter ( ( t ) => t . serverName === server . name )
725-
726743 const enabledTools = [ ]
727744 const disabledTools = [ ]
728745
729746 for ( const tool of server . tools ) {
730- let isEnabled = true
731-
732- // Check if there are specific allowed tools for this server
733- if ( serverAllowedTools . length > 0 ) {
734- isEnabled = serverAllowedTools . some ( ( allowedTool ) =>
735- patternMatching . matchesPattern ( tool . name , allowedTool . toolName ) ,
736- )
737- } else if ( serverDisallowedTools . length > 0 ) {
738- // Check if tool is specifically disallowed
739- isEnabled = ! serverDisallowedTools . some ( ( disallowedTool ) =>
740- patternMatching . matchesPattern ( tool . name , disallowedTool . toolName ) ,
741- )
742- }
747+ const isEnabled = resolveToolAccess ( tool . name , server . name )
743748
744749 if ( isEnabled ) {
745750 enabledTools . push ( { ...tool , isEnabled : true } )
@@ -1002,6 +1007,7 @@ function ServerPatternRules({
10021007 const { t } = useAppTranslation ( )
10031008 const [ newAllowPattern , setNewAllowPattern ] = useState ( "" )
10041009 const [ newBlockPattern , setNewBlockPattern ] = useState ( "" )
1010+ const [ showPatternHelp , setShowPatternHelp ] = useState ( false )
10051011
10061012 // Get pattern rules specific to this server
10071013 const serverAllowedPatterns = allowedTools
@@ -1115,19 +1121,102 @@ function ServerPatternRules({
11151121 </ div >
11161122
11171123 { /* Pattern Help */ }
1118- < div className = "text-xs text-vscode-descriptionForeground p-2 bg-vscode-textCodeBlock-background rounded" >
1119- < div className = "font-medium mb-1" > { t ( "prompts:mcpRestrictions.patterns.help" ) } </ div >
1120- < div className = "space-y-0.5" >
1121- < div >
1122- < code > *</ code > - { t ( "prompts:mcpRestrictions.patterns.wildcard" ) }
1123- </ div >
1124- < div >
1125- < code > get_*</ code > - { t ( "prompts:mcpRestrictions.patterns.example1" ) }
1126- </ div >
1127- < div >
1128- < code > *_admin</ code > - { t ( "prompts:mcpRestrictions.patterns.example2" ) }
1124+ < div className = "border border-vscode-widget-border rounded" >
1125+ < div
1126+ className = "flex items-center justify-between p-2 cursor-pointer hover:bg-vscode-list-hoverBackground"
1127+ onClick = { ( ) => setShowPatternHelp ( ! showPatternHelp ) } >
1128+ < div className = "text-xs font-medium text-vscode-foreground" >
1129+ { t ( "prompts:mcpRestrictions.patterns.help" ) }
11291130 </ div >
1131+ < ChevronDown className = { cn ( "w-3 h-3 transition-transform" , { "rotate-180" : showPatternHelp } ) } />
11301132 </ div >
1133+
1134+ { showPatternHelp && (
1135+ < div className = "border-t border-vscode-widget-border p-2 text-xs text-vscode-descriptionForeground bg-vscode-textCodeBlock-background" >
1136+ { /* Basic Patterns */ }
1137+ < div className = "mb-3" >
1138+ < div className = "font-medium mb-1 text-vscode-foreground" >
1139+ { t ( "prompts:mcpRestrictions.patterns.basicPatterns" ) }
1140+ </ div >
1141+ < div className = "space-y-0.5" >
1142+ < div >
1143+ < code className = "text-vscode-textPreformat-foreground" > *</ code > -{ " " }
1144+ { t ( "prompts:mcpRestrictions.patterns.wildcard" ) }
1145+ </ div >
1146+ < div >
1147+ < code className = "text-vscode-textPreformat-foreground" > ?</ code > -{ " " }
1148+ { t ( "prompts:mcpRestrictions.patterns.singleChar" ) }
1149+ </ div >
1150+ < div >
1151+ < code className = "text-vscode-textPreformat-foreground" > get_*</ code > -{ " " }
1152+ { t ( "prompts:mcpRestrictions.patterns.example1" ) }
1153+ </ div >
1154+ < div >
1155+ < code className = "text-vscode-textPreformat-foreground" > *_admin</ code > -{ " " }
1156+ { t ( "prompts:mcpRestrictions.patterns.example2" ) }
1157+ </ div >
1158+ < div >
1159+ < code className = "text-vscode-textPreformat-foreground" > delete_*</ code > -{ " " }
1160+ { t ( "prompts:mcpRestrictions.patterns.example3" ) }
1161+ </ div >
1162+ </ div >
1163+ </ div >
1164+
1165+ { /* Pattern Specificity Rules */ }
1166+ < div className = "mb-3" >
1167+ < div className = "font-medium mb-1 text-vscode-foreground" >
1168+ { t ( "prompts:mcpRestrictions.patterns.specificity" ) }
1169+ </ div >
1170+ < div className = "space-y-1" >
1171+ < div className = "flex items-start gap-2" >
1172+ < div className = "w-1.5 h-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0" />
1173+ < span > { t ( "prompts:mcpRestrictions.patterns.exactWins" ) } </ span >
1174+ </ div >
1175+ < div className = "flex items-start gap-2" >
1176+ < div className = "w-1.5 h-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0" />
1177+ < span > { t ( "prompts:mcpRestrictions.patterns.longerWins" ) } </ span >
1178+ </ div >
1179+ < div className = "flex items-start gap-2" >
1180+ < div className = "w-1.5 h-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0" />
1181+ < span > { t ( "prompts:mcpRestrictions.patterns.conflictResolution" ) } </ span >
1182+ </ div >
1183+ </ div >
1184+ </ div >
1185+
1186+ { /* Specificity Examples */ }
1187+ < div >
1188+ < div className = "font-medium mb-1 text-vscode-foreground" >
1189+ { t ( "prompts:mcpRestrictions.patterns.specificityExamples" ) }
1190+ </ div >
1191+ < div className = "space-y-1" >
1192+ < div className = "p-1.5 bg-vscode-editor-background rounded border border-vscode-panel-border" >
1193+ < div className = "flex items-center gap-1 mb-0.5" >
1194+ < code className = "text-red-300" > block "*"</ code >
1195+ < span className = "text-vscode-descriptionForeground" > +</ span >
1196+ < code className = "text-green-300" > allow "get_file"</ code >
1197+ < span className = "text-vscode-descriptionForeground" > →</ span >
1198+ < span className = "text-green-300 font-medium" > get_file allowed</ span >
1199+ </ div >
1200+ < div className = "text-vscode-descriptionForeground ml-2" >
1201+ { t ( "prompts:mcpRestrictions.patterns.exactExample" ) }
1202+ </ div >
1203+ </ div >
1204+ < div className = "p-1.5 bg-vscode-editor-background rounded border border-vscode-panel-border" >
1205+ < div className = "flex items-center gap-1 mb-0.5" >
1206+ < code className = "text-red-300" > block "*"</ code >
1207+ < span className = "text-vscode-descriptionForeground" > +</ span >
1208+ < code className = "text-green-300" > allow "core_*"</ code >
1209+ < span className = "text-vscode-descriptionForeground" > →</ span >
1210+ < span className = "text-green-300 font-medium" > core_list allowed</ span >
1211+ </ div >
1212+ < div className = "text-vscode-descriptionForeground ml-2" >
1213+ { t ( "prompts:mcpRestrictions.patterns.wildcardExample" ) }
1214+ </ div >
1215+ </ div >
1216+ </ div >
1217+ </ div >
1218+ </ div >
1219+ ) }
11311220 </ div >
11321221 </ div >
11331222 )
0 commit comments