@@ -22,10 +22,11 @@ export type ToolPolicy = z.infer<typeof ToolPolicySchema>;
2222 * @returns Filtered tools based on policy
2323 *
2424 * Algorithm:
25- * 1. Check if any tool is marked as "require"
26- * 2. If a tool is required, disable all other tools (at most one can be required)
27- * 3. Otherwise, start with default "allow" for all tools and apply filters in order
28- * 4. Last matching filter wins
25+ * - Filters are applied in order, with default behavior "allow all".
26+ * - If any tool is marked as "require" (at most one tool may match across the whole policy),
27+ * only that tool remains eligible. Later filters may still disable it (resulting in no tools).
28+ * - Without a required tool, enable/disable filters apply to all tools, and the last matching
29+ * filter wins for each tool.
2930 */
3031export function applyToolPolicy (
3132 tools : Record < string , Tool > ,
@@ -36,40 +37,57 @@ export function applyToolPolicy(
3637 return tools ;
3738 }
3839
39- // First pass: find any required tools
40- const requiredTools = new Set < string > ( ) ;
40+ const toolNames = Object . keys ( tools ) ;
41+
42+ // First pass: find a single required tool (if any), validating that the policy
43+ // never results in multiple required matches.
44+ let requiredTool : string | null = null ;
4145 for ( const filter of policy ) {
42- if ( filter . action === "require" ) {
43- const regex = new RegExp ( `^${ filter . regex_match } $` ) ;
44- for ( const toolName of Object . keys ( tools ) ) {
45- if ( regex . test ( toolName ) ) {
46- requiredTools . add ( toolName ) ;
47- }
48- }
46+ if ( filter . action !== "require" ) continue ;
47+
48+ const regex = new RegExp ( `^${ filter . regex_match } $` ) ;
49+ const matches = toolNames . filter ( ( toolName ) => regex . test ( toolName ) ) ;
50+
51+ if ( matches . length > 1 ) {
52+ throw new Error (
53+ `Tool policy error: Multiple tools marked as required (${ matches . join ( ", " ) } ). At most one tool can be required.`
54+ ) ;
4955 }
50- }
56+ if ( matches . length === 0 ) continue ;
5157
52- // Validate: at most one tool can be required
53- if ( requiredTools . size > 1 ) {
54- throw new Error (
55- `Tool policy error: Multiple tools marked as required (${ Array . from ( requiredTools ) . join ( ", " ) } ). At most one tool can be required.`
56- ) ;
58+ if ( requiredTool && requiredTool !== matches [ 0 ] ) {
59+ throw new Error (
60+ `Tool policy error: Multiple tools marked as required (${ requiredTool } , ${ matches [ 0 ] } ). At most one tool can be required.`
61+ ) ;
62+ }
63+ requiredTool = matches [ 0 ] ;
5764 }
5865
59- // If a tool is required, return only that tool
60- if ( requiredTools . size === 1 ) {
61- const requiredTool = Array . from ( requiredTools ) [ 0 ] ;
62- return {
63- [ requiredTool ] : tools [ requiredTool ] ,
64- } ;
66+ // If a tool is required, only that tool remains eligible, but later filters may disable it.
67+ if ( requiredTool ) {
68+ let enabled = true ; // Default allow
69+
70+ for ( const filter of policy ) {
71+ const regex = new RegExp ( `^${ filter . regex_match } $` ) ;
72+ if ( ! regex . test ( requiredTool ) ) continue ;
73+
74+ if ( filter . action === "disable" ) {
75+ enabled = false ;
76+ continue ;
77+ }
78+ // enable/require both imply enabled for the required tool at this point in the policy
79+ enabled = true ;
80+ }
81+
82+ return enabled ? { [ requiredTool ] : tools [ requiredTool ] } : { } ;
6583 }
6684
6785 // No required tools: apply standard enable/disable logic
6886 // Build a map of tool name -> enabled status
6987 const toolStatus = new Map < string , boolean > ( ) ;
7088
7189 // Initialize all tools as enabled (default allow)
72- for ( const toolName of Object . keys ( tools ) ) {
90+ for ( const toolName of toolNames ) {
7391 toolStatus . set ( toolName , true ) ;
7492 }
7593
@@ -81,7 +99,7 @@ export function applyToolPolicy(
8199 const shouldEnable = filter . action === "enable" ;
82100
83101 // Apply filter to matching tools
84- for ( const toolName of Object . keys ( tools ) ) {
102+ for ( const toolName of toolNames ) {
85103 if ( regex . test ( toolName ) ) {
86104 toolStatus . set ( toolName , shouldEnable ) ;
87105 }
0 commit comments