Skip to content

Commit b77fe87

Browse files
author
Evyatar Mitrani
committed
temp
1 parent bfaf398 commit b77fe87

File tree

3 files changed

+185
-65
lines changed

3 files changed

+185
-65
lines changed

webview-ui/src/components/modes/McpRestrictionsEditor.tsx

Lines changed: 152 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -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 &quot;*&quot;</code>
1195+
<span className="text-vscode-descriptionForeground">+</span>
1196+
<code className="text-green-300">allow &quot;get_file&quot;</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 &quot;*&quot;</code>
1207+
<span className="text-vscode-descriptionForeground">+</span>
1208+
<code className="text-green-300">allow &quot;core_*&quot;</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
)

webview-ui/src/i18n/locales/en/prompts.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,17 @@
250250
"example1": "Matches docs-api, docs-search, etc.",
251251
"example2": "Matches server-admin, user-admin, etc.",
252252
"example3": "Matches delete_file, delete_user, etc.",
253-
"help": "Pattern Help"
253+
"help": "Pattern Help",
254+
"specificity": "Pattern Specificity Rules",
255+
"exactWins": "Exact matches (no wildcards) always take priority",
256+
"longerWins": "Among wildcard patterns, longer/more specific patterns win",
257+
"conflictResolution": "When a tool matches both allow and block patterns, the most specific pattern determines the result",
258+
"showHelp": "Show pattern help",
259+
"hideHelp": "Hide pattern help",
260+
"basicPatterns": "Basic Patterns",
261+
"specificityExamples": "Specificity Examples",
262+
"exactExample": "Exact match beats any wildcard pattern",
263+
"wildcardExample": "More specific wildcard beats general wildcard"
254264
},
255265
"status": {
256266
"enabled": "ENABLED",

webview-ui/src/lib/utils.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { clsx, type ClassValue } from "clsx"
22
import { twMerge } from "tailwind-merge"
3-
import { matchesPatternOrContains, filterByPattern } from "../../../src/shared/pattern-matching"
3+
import {
4+
matchesPatternOrContains,
5+
filterByPattern,
6+
matchesGlobPattern,
7+
findMostSpecificMatchingPattern,
8+
} from "../../../src/shared/pattern-matching"
49

510
export function cn(...inputs: ClassValue[]) {
611
return twMerge(clsx(inputs))
@@ -19,6 +24,22 @@ export const patternMatching = {
1924
*/
2025
matchesPattern: matchesPatternOrContains,
2126

27+
/**
28+
* Check if a string matches a glob pattern (exact matching for restrictions)
29+
* @param text - Text to test against pattern
30+
* @param pattern - Glob pattern or exact string
31+
* @returns boolean indicating if text matches pattern
32+
*/
33+
matchesGlobPattern: matchesGlobPattern,
34+
35+
/**
36+
* Find the most specific pattern that matches the given text
37+
* @param text - Text to match against
38+
* @param patterns - Array of patterns to check
39+
* @returns Most specific matching pattern, or null if no match
40+
*/
41+
findMostSpecificMatchingPattern: findMostSpecificMatchingPattern,
42+
2243
/**
2344
* Filter an array of items by pattern matching against specified fields
2445
* @param items - Array of items to filter

0 commit comments

Comments
 (0)