@@ -533,7 +533,6 @@ export async function createPolicyEngineConfig(
533533 disableAlwaysAllow : settings . disableAlwaysAllow ,
534534 } ;
535535}
536-
537536interface TomlRule {
538537 toolName ?: string ;
539538 mcpName ?: string ;
@@ -542,10 +541,64 @@ interface TomlRule {
542541 commandPrefix ?: string | string [ ] ;
543542 argsPattern ?: string ;
544543 allowRedirection ?: boolean ;
544+ modes ?: ApprovalMode [ ] ;
545545 // Index signature to satisfy Record type if needed for toml.stringify
546546 [ key : string ] : unknown ;
547547}
548548
549+ /**
550+ * Finds a rule in the rule array that matches the given criteria.
551+ */
552+ function findMatchingRule (
553+ rules : TomlRule [ ] ,
554+ criteria : {
555+ toolName : string ;
556+ mcpName ?: string ;
557+ commandPrefix ?: string | string [ ] ;
558+ argsPattern ?: string ;
559+ } ,
560+ ) : TomlRule | undefined {
561+ return rules . find (
562+ ( r ) =>
563+ r . toolName === criteria . toolName &&
564+ r . mcpName === criteria . mcpName &&
565+ JSON . stringify ( r . commandPrefix ) ===
566+ JSON . stringify ( criteria . commandPrefix ) &&
567+ r . argsPattern === criteria . argsPattern ,
568+ ) ;
569+ }
570+
571+ /**
572+ * Creates a new TOML rule object from the given tool name and message.
573+ */
574+ function createTomlRule ( toolName : string , message : UpdatePolicy ) : TomlRule {
575+ const rule : TomlRule = {
576+ decision : 'allow' ,
577+ priority : getAlwaysAllowPriorityFraction ( ) ,
578+ toolName,
579+ } ;
580+
581+ if ( message . mcpName ) {
582+ rule . mcpName = message . mcpName ;
583+ }
584+
585+ if ( message . commandPrefix ) {
586+ rule . commandPrefix = message . commandPrefix ;
587+ } else if ( message . argsPattern ) {
588+ rule . argsPattern = message . argsPattern ;
589+ }
590+
591+ if ( message . allowRedirection !== undefined ) {
592+ rule . allowRedirection = message . allowRedirection ;
593+ }
594+
595+ if ( message . modes ) {
596+ rule . modes = message . modes ;
597+ }
598+
599+ return rule ;
600+ }
601+
549602export function createPolicyUpdater (
550603 policyEngine : PolicyEngine ,
551604 messageBus : MessageBus ,
@@ -585,6 +638,7 @@ export function createPolicyUpdater(
585638 priority,
586639 argsPattern : new RegExp ( pattern ) ,
587640 mcpName : message . mcpName ,
641+ modes : message . modes ,
588642 source : 'Dynamic (Confirmed)' ,
589643 allowRedirection : message . allowRedirection ,
590644 } ) ;
@@ -622,6 +676,7 @@ export function createPolicyUpdater(
622676 priority,
623677 argsPattern,
624678 mcpName : message . mcpName ,
679+ modes : message . modes ,
625680 source : 'Dynamic (Confirmed)' ,
626681 allowRedirection : message . allowRedirection ,
627682 } ) ;
@@ -662,39 +717,36 @@ export function createPolicyUpdater(
662717 existingData . rule = [ ] ;
663718 }
664719
665- // Create new rule object
666- const newRule : TomlRule = {
667- decision : 'allow' ,
668- priority : getAlwaysAllowPriorityFraction ( ) ,
669- } ;
670-
720+ // Normalize tool name for MCP
721+ let normalizedToolName = toolName ;
671722 if ( message . mcpName ) {
672- newRule . mcpName = message . mcpName ;
673-
674723 const expectedPrefix = `${ MCP_TOOL_PREFIX } ${ message . mcpName } _` ;
675724 if ( toolName . startsWith ( expectedPrefix ) ) {
676- newRule . toolName = toolName . slice ( expectedPrefix . length ) ;
677- } else {
678- newRule . toolName = toolName ;
725+ normalizedToolName = toolName . slice ( expectedPrefix . length ) ;
679726 }
680- } else {
681- newRule . toolName = toolName ;
682727 }
683728
684- if ( message . commandPrefix ) {
685- newRule . commandPrefix = message . commandPrefix ;
686- } else if ( message . argsPattern ) {
687- // message.argsPattern was already validated above
688- newRule . argsPattern = message . argsPattern ;
689- }
729+ // Look for an existing rule to update
730+ const existingRule = findMatchingRule ( existingData . rule , {
731+ toolName : normalizedToolName ,
732+ mcpName : message . mcpName ,
733+ commandPrefix : message . commandPrefix ,
734+ argsPattern : message . argsPattern ,
735+ } ) ;
690736
691- if ( message . allowRedirection !== undefined ) {
692- newRule . allowRedirection = message . allowRedirection ;
737+ if ( existingRule ) {
738+ if ( message . allowRedirection !== undefined ) {
739+ existingRule . allowRedirection = message . allowRedirection ;
740+ }
741+ if ( message . modes ) {
742+ existingRule . modes = message . modes ;
743+ }
744+ } else {
745+ existingData . rule . push (
746+ createTomlRule ( normalizedToolName , message ) ,
747+ ) ;
693748 }
694749
695- // Add to rules
696- existingData . rule . push ( newRule ) ;
697-
698750 // Serialize back to TOML
699751 // @iarna /toml stringify might not produce beautiful output but it handles escaping correctly
700752 // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
0 commit comments