@@ -25,15 +25,19 @@ import {
2525 hasRedirection ,
2626} from '../utils/shell-utils.js' ;
2727import { getToolAliases } from '../tools/tool-names.js' ;
28- import { MCP_TOOL_PREFIX } from '../tools/mcp-tool.js' ;
28+ import {
29+ MCP_TOOL_PREFIX ,
30+ isMcpToolAnnotation ,
31+ parseMcpToolName ,
32+ } from '../tools/mcp-tool.js' ;
2933
3034function isWildcardPattern ( name : string ) : boolean {
3135 return name === '*' || name . includes ( '*' ) ;
3236}
3337
3438/**
3539 * Checks if a tool call matches a wildcard pattern.
36- * Supports global (*) and the new explicit MCP (*mcp_serverName_**) format.
40+ * Supports global (*) and the explicit MCP (*mcp_serverName_**) format.
3741 */
3842function matchesWildcard (
3943 pattern : string ,
@@ -91,7 +95,7 @@ function ruleMatches(
9195
9296 // Check tool name if specified
9397 if ( rule . toolName ) {
94- // Support wildcard patterns: "serverName__ *" matches "serverName__anyTool "
98+ // Support wildcard patterns: "mcp_serverName_ *" matches "mcp_serverName_anyTool "
9599 if ( rule . toolName === '*' ) {
96100 // Match all tools
97101 } else if ( isWildcardPattern ( rule . toolName ) ) {
@@ -349,18 +353,19 @@ export class PolicyEngine {
349353 serverName : string | undefined ,
350354 toolAnnotations ?: Record < string , unknown > ,
351355 ) : Promise < CheckResult > {
352- if (
353- ! serverName &&
354- toolAnnotations &&
355- typeof toolAnnotations [ '_serverName' ] === 'string'
356- ) {
357- serverName = toolAnnotations [ '_serverName' ] ;
356+ // Case 1: Metadata injection is the primary and safest way to identify an MCP server.
357+ // If we have explicit `_serverName` metadata (usually injected by tool-registry for active tools), use it.
358+ if ( ! serverName && isMcpToolAnnotation ( toolAnnotations ) ) {
359+ serverName = toolAnnotations . _serverName ;
358360 }
359361
360- if ( ! serverName && toolCall . name ?. startsWith ( MCP_TOOL_PREFIX ) ) {
361- const parts = toolCall . name . split ( '_' ) ;
362- if ( parts . length >= 3 ) {
363- serverName = parts [ 1 ] ;
362+ // Case 2: Fallback for static FQN strings (e.g. from TOML policies or allowed/excluded settings strings).
363+ // These strings don't have active metadata objects associated with them during policy generation,
364+ // so we must extract the server name from the qualified `mcp_{server}_{tool}` format.
365+ if ( ! serverName && toolCall . name ) {
366+ const parsed = parseMcpToolName ( toolCall . name ) ;
367+ if ( parsed . serverName ) {
368+ serverName = parsed . serverName ;
364369 }
365370 }
366371
@@ -397,20 +402,12 @@ export class PolicyEngine {
397402 let matchedRule : PolicyRule | undefined ;
398403 let decision : PolicyDecision | undefined ;
399404
400- // For tools with a server name, we want to try matching both the
401- // original name and the fully qualified name (server__tool).
402405 // We also want to check legacy aliases for the tool name.
403406 const toolNamesToTry = toolCall . name ? getToolAliases ( toolCall . name ) : [ ] ;
404407
405408 const toolCallsToTry : FunctionCall [ ] = [ ] ;
406409 for ( const name of toolNamesToTry ) {
407410 toolCallsToTry . push ( { ...toolCall , name } ) ;
408- if ( serverName && ! name . includes ( '__' ) ) {
409- toolCallsToTry . push ( {
410- ...toolCall ,
411- name : `${ serverName } __${ name } ` ,
412- } ) ;
413- }
414411 }
415412
416413 for ( const rule of this . rules ) {
@@ -654,9 +651,9 @@ export class PolicyEngine {
654651
655652 for ( const toolName of allToolNames ) {
656653 const annotations = toolMetadata ?. get ( toolName ) ;
657- const rawServerName = annotations ?. [ '_serverName' ] ;
658- const serverName =
659- typeof rawServerName === 'string' ? rawServerName : undefined ;
654+ const serverName = isMcpToolAnnotation ( annotations )
655+ ? annotations . _serverName
656+ : undefined ;
660657
661658 let staticallyExcluded = false ;
662659 let matchFound = false ;
0 commit comments