@google/gemini-cli-core 0.33.0-preview.4 → 0.33.0-preview.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/google-gemini-cli-core-0.33.0-preview.4.tgz +0 -0
  2. package/dist/src/agents/local-executor.test.js +2 -2
  3. package/dist/src/agents/local-executor.test.js.map +1 -1
  4. package/dist/src/core/loggingContentGenerator.test.js +9 -10
  5. package/dist/src/core/loggingContentGenerator.test.js.map +1 -1
  6. package/dist/src/core/prompts.test.js +4 -4
  7. package/dist/src/core/prompts.test.js.map +1 -1
  8. package/dist/src/generated/git-commit.d.ts +2 -2
  9. package/dist/src/generated/git-commit.js +2 -2
  10. package/dist/src/policy/config.js +12 -4
  11. package/dist/src/policy/config.js.map +1 -1
  12. package/dist/src/policy/config.test.js +17 -23
  13. package/dist/src/policy/config.test.js.map +1 -1
  14. package/dist/src/policy/policy-engine.js +80 -161
  15. package/dist/src/policy/policy-engine.js.map +1 -1
  16. package/dist/src/policy/policy-engine.test.js +180 -110
  17. package/dist/src/policy/policy-engine.test.js.map +1 -1
  18. package/dist/src/policy/toml-loader.d.ts +1 -0
  19. package/dist/src/policy/toml-loader.js +26 -19
  20. package/dist/src/policy/toml-loader.js.map +1 -1
  21. package/dist/src/policy/toml-loader.test.js +36 -18
  22. package/dist/src/policy/toml-loader.test.js.map +1 -1
  23. package/dist/src/policy/types.d.ts +10 -0
  24. package/dist/src/policy/types.js.map +1 -1
  25. package/dist/src/prompts/promptProvider.test.js +1 -1
  26. package/dist/src/prompts/promptProvider.test.js.map +1 -1
  27. package/dist/src/tools/mcp-tool.d.ts +38 -4
  28. package/dist/src/tools/mcp-tool.js +72 -12
  29. package/dist/src/tools/mcp-tool.js.map +1 -1
  30. package/dist/src/tools/mcp-tool.test.js +34 -17
  31. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  32. package/dist/src/tools/tool-registry.js +9 -7
  33. package/dist/src/tools/tool-registry.js.map +1 -1
  34. package/dist/src/tools/tool-registry.test.js +27 -30
  35. package/dist/src/tools/tool-registry.test.js.map +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/dist/google-gemini-cli-core-0.33.0-preview.3.tgz +0 -0
@@ -101,19 +101,15 @@ describe('PolicyEngine', () => {
101
101
  it('should match unqualified tool names with qualified rules when serverName is provided', async () => {
102
102
  const rules = [
103
103
  {
104
- toolName: 'my-server__tool',
104
+ toolName: 'mcp_my-server_tool',
105
+ mcpName: 'my-server',
105
106
  decision: PolicyDecision.ALLOW,
106
107
  },
107
108
  ];
108
109
  engine = new PolicyEngine({ rules });
109
110
  // Match with qualified name (standard)
110
- expect((await engine.check({ name: 'my-server__tool' }, 'my-server')).decision).toBe(PolicyDecision.ALLOW);
111
- // Match with unqualified name + serverName (the fix)
112
- expect((await engine.check({ name: 'tool' }, 'my-server')).decision).toBe(PolicyDecision.ALLOW);
113
- // Should NOT match with unqualified name but NO serverName
114
- expect((await engine.check({ name: 'tool' }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
115
- // Should NOT match with unqualified name but WRONG serverName
116
- expect((await engine.check({ name: 'tool' }, 'wrong-server')).decision).toBe(PolicyDecision.ASK_USER);
111
+ expect((await engine.check({ name: 'mcp_my-server_tool' }, 'my-server'))
112
+ .decision).toBe(PolicyDecision.ALLOW);
117
113
  });
118
114
  it('should match by args pattern', async () => {
119
115
  const rules = [
@@ -328,58 +324,45 @@ describe('PolicyEngine', () => {
328
324
  ],
329
325
  });
330
326
  expect((await engine.check({ name: 'read_file' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
331
- expect((await engine.check({ name: 'my-server__tool' }, 'my-server')).decision).toBe(PolicyDecision.ALLOW);
327
+ expect((await engine.check({ name: 'mcp_my-server_tool' }, 'my-server'))
328
+ .decision).toBe(PolicyDecision.ALLOW);
332
329
  });
333
- it('should match any MCP tool when toolName is *__*', async () => {
330
+ it('should match any MCP tool when toolName is mcp_*', async () => {
334
331
  engine = new PolicyEngine({
335
332
  rules: [
336
- { toolName: '*__*', decision: PolicyDecision.ALLOW, priority: 10 },
333
+ { toolName: 'mcp_*', decision: PolicyDecision.ALLOW, priority: 10 },
337
334
  ],
338
335
  defaultDecision: PolicyDecision.DENY,
339
336
  });
340
- expect((await engine.check({ name: 'mcp__tool' }, 'mcp')).decision).toBe(PolicyDecision.ALLOW);
341
- expect((await engine.check({ name: 'other__tool' }, 'other')).decision).toBe(PolicyDecision.ALLOW);
337
+ expect((await engine.check({ name: 'mcp_mcp_tool' }, 'mcp')).decision).toBe(PolicyDecision.ALLOW);
338
+ expect((await engine.check({ name: 'mcp_other_tool' }, 'other')).decision).toBe(PolicyDecision.ALLOW);
342
339
  expect((await engine.check({ name: 'read_file' }, undefined)).decision).toBe(PolicyDecision.DENY);
343
340
  });
344
- it('should match specific tool across all servers when using *__tool', async () => {
345
- engine = new PolicyEngine({
346
- rules: [
347
- {
348
- toolName: '*__search',
349
- decision: PolicyDecision.ALLOW,
350
- priority: 10,
351
- },
352
- ],
353
- defaultDecision: PolicyDecision.DENY,
354
- });
355
- expect((await engine.check({ name: 'ws__search' }, 'ws')).decision).toBe(PolicyDecision.ALLOW);
356
- expect((await engine.check({ name: 'gh__search' }, 'gh')).decision).toBe(PolicyDecision.ALLOW);
357
- expect((await engine.check({ name: 'gh__list' }, 'gh')).decision).toBe(PolicyDecision.DENY);
358
- });
359
341
  it('should match MCP server wildcard patterns', async () => {
360
342
  const rules = [
361
343
  {
362
- toolName: 'my-server__*',
344
+ toolName: 'mcp_my-server_*',
345
+ mcpName: 'my-server',
363
346
  decision: PolicyDecision.ALLOW,
364
347
  priority: 10,
365
348
  },
366
349
  {
367
- toolName: 'blocked-server__*',
350
+ toolName: 'mcp_blocked-server_*',
351
+ mcpName: 'blocked-server',
368
352
  decision: PolicyDecision.DENY,
369
353
  priority: 20,
370
354
  },
371
355
  ];
372
356
  engine = new PolicyEngine({ rules });
373
357
  // Should match my-server tools
374
- expect((await engine.check({ name: 'my-server__tool1' }, 'my-server'))
375
- .decision).toBe(PolicyDecision.ALLOW);
376
- expect((await engine.check({ name: 'my-server__another_tool' }, 'my-server'))
358
+ expect((await engine.check({ name: 'mcp_my-server_tool1' }, 'my-server'))
377
359
  .decision).toBe(PolicyDecision.ALLOW);
360
+ expect((await engine.check({ name: 'mcp_my-server_another_tool' }, 'my-server')).decision).toBe(PolicyDecision.ALLOW);
378
361
  // Should match blocked-server tools
379
- expect((await engine.check({ name: 'blocked-server__tool1' }, 'blocked-server')).decision).toBe(PolicyDecision.DENY);
380
- expect((await engine.check({ name: 'blocked-server__dangerous' }, 'blocked-server')).decision).toBe(PolicyDecision.DENY);
362
+ expect((await engine.check({ name: 'mcp_blocked-server_tool1' }, 'blocked-server')).decision).toBe(PolicyDecision.DENY);
363
+ expect((await engine.check({ name: 'mcp_blocked-server_dangerous' }, 'blocked-server')).decision).toBe(PolicyDecision.DENY);
381
364
  // Should not match other patterns
382
- expect((await engine.check({ name: 'other-server__tool' }, 'other-server'))
365
+ expect((await engine.check({ name: 'mcp_other-server_tool' }, 'other-server'))
383
366
  .decision).toBe(PolicyDecision.ASK_USER);
384
367
  expect((await engine.check({ name: 'my-server-tool' }, undefined)).decision).toBe(PolicyDecision.ASK_USER); // No __ separator
385
368
  expect((await engine.check({ name: 'my-server' }, undefined)).decision).toBe(PolicyDecision.ASK_USER); // No tool name
@@ -387,61 +370,65 @@ describe('PolicyEngine', () => {
387
370
  it('should prioritize specific tool rules over server wildcards', async () => {
388
371
  const rules = [
389
372
  {
390
- toolName: 'my-server__*',
373
+ toolName: 'mcp_my-server_*',
374
+ mcpName: 'my-server',
391
375
  decision: PolicyDecision.ALLOW,
392
376
  priority: 10,
393
377
  },
394
378
  {
395
- toolName: 'my-server__dangerous-tool',
379
+ toolName: 'mcp_my-server_dangerous-tool',
380
+ mcpName: 'my-server',
396
381
  decision: PolicyDecision.DENY,
397
382
  priority: 20,
398
383
  },
399
384
  ];
400
385
  engine = new PolicyEngine({ rules });
401
386
  // Specific tool deny should override server allow
402
- expect((await engine.check({ name: 'my-server__dangerous-tool' }, 'my-server'))
403
- .decision).toBe(PolicyDecision.DENY);
404
- expect((await engine.check({ name: 'my-server__safe-tool' }, 'my-server'))
387
+ expect((await engine.check({ name: 'mcp_my-server_dangerous-tool' }, 'my-server')).decision).toBe(PolicyDecision.DENY);
388
+ expect((await engine.check({ name: 'mcp_my-server_safe-tool' }, 'my-server'))
405
389
  .decision).toBe(PolicyDecision.ALLOW);
406
390
  });
407
391
  it('should NOT match spoofed server names when using wildcards', async () => {
408
- // Vulnerability: A rule for 'prefix__*' matches 'prefix__suffix__tool'
409
- // effectively allowing a server named 'prefix__suffix' to spoof 'prefix'.
392
+ // Vulnerability: A rule for 'mcp_prefix_*' matches 'mcp_prefix__suffix_tool'
393
+ // effectively allowing a server named 'mcp_prefix_suffix' to spoof 'prefix'.
410
394
  const rules = [
411
395
  {
412
- toolName: 'safe_server__*',
396
+ toolName: 'mcp_safe_server_*',
397
+ mcpName: 'safe_server',
413
398
  decision: PolicyDecision.ALLOW,
414
399
  },
415
400
  ];
416
401
  engine = new PolicyEngine({ rules });
417
- // A tool from a different server 'safe_server__malicious'
418
- const spoofedToolCall = { name: 'safe_server__malicious__tool' };
402
+ // A tool from a different server 'mcp_safe_server_malicious'
403
+ const spoofedToolCall = { name: 'mcp_mcp_safe_server_malicious_tool' };
419
404
  // CURRENT BEHAVIOR (FIXED): Matches because it starts with 'safe_server__' BUT serverName doesn't match 'safe_server'
420
405
  // We expect this to FAIL matching the ALLOW rule, thus falling back to default (ASK_USER)
421
- expect((await engine.check(spoofedToolCall, 'safe_server__malicious'))
406
+ expect((await engine.check(spoofedToolCall, 'mcp_safe_server_malicious'))
422
407
  .decision).toBe(PolicyDecision.ASK_USER);
423
408
  });
424
409
  it('should verify tool name prefix even if serverName matches', async () => {
425
410
  const rules = [
426
411
  {
427
- toolName: 'safe_server__*',
412
+ toolName: 'mcp_safe_server_*',
413
+ mcpName: 'safe_server',
428
414
  decision: PolicyDecision.ALLOW,
429
415
  },
430
416
  ];
431
417
  engine = new PolicyEngine({ rules });
432
418
  // serverName matches, but tool name does not start with prefix
433
- const invalidToolCall = { name: 'other_server__tool' };
419
+ const invalidToolCall = { name: 'mcp_other_server_tool' };
434
420
  expect((await engine.check(invalidToolCall, 'safe_server')).decision).toBe(PolicyDecision.ASK_USER);
435
421
  });
436
422
  it('should allow when both serverName and tool name prefix match', async () => {
437
423
  const rules = [
438
424
  {
439
- toolName: 'safe_server__*',
425
+ toolName: 'mcp_safe_server_*',
426
+ mcpName: 'safe_server',
440
427
  decision: PolicyDecision.ALLOW,
441
428
  },
442
429
  ];
443
430
  engine = new PolicyEngine({ rules });
444
- const validToolCall = { name: 'safe_server__tool' };
431
+ const validToolCall = { name: 'mcp_safe_server_tool' };
445
432
  expect((await engine.check(validToolCall, 'safe_server')).decision).toBe(PolicyDecision.ALLOW);
446
433
  });
447
434
  });
@@ -1409,17 +1396,22 @@ describe('PolicyEngine', () => {
1409
1396
  });
1410
1397
  it('should support wildcard patterns for checkers', async () => {
1411
1398
  const rules = [
1412
- { toolName: 'server__tool', decision: PolicyDecision.ALLOW },
1399
+ {
1400
+ toolName: 'mcp_server_tool',
1401
+ mcpName: 'server',
1402
+ decision: PolicyDecision.ALLOW,
1403
+ },
1413
1404
  ];
1414
1405
  const wildcardChecker = {
1415
1406
  checker: { type: 'external', name: 'wildcard' },
1416
- toolName: 'server__*',
1407
+ toolName: 'mcp_server_*',
1408
+ mcpName: 'server',
1417
1409
  };
1418
1410
  engine = new PolicyEngine({ rules, checkers: [wildcardChecker] }, mockCheckerRunner);
1419
1411
  vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
1420
1412
  decision: SafetyCheckDecision.ALLOW,
1421
1413
  });
1422
- await engine.check({ name: 'server__tool' }, 'server');
1414
+ await engine.check({ name: 'mcp_server_tool' }, 'server');
1423
1415
  expect(mockCheckerRunner.runChecker).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ name: 'wildcard' }));
1424
1416
  });
1425
1417
  it('should run safety checkers when decision is ASK_USER and downgrade to DENY on failure', async () => {
@@ -1509,11 +1501,13 @@ describe('PolicyEngine', () => {
1509
1501
  {
1510
1502
  name: 'should return empty set when no rules provided',
1511
1503
  rules: [],
1504
+ allToolNames: ['tool1'],
1512
1505
  expected: [],
1513
1506
  },
1514
1507
  {
1515
1508
  name: 'should apply rules without explicit modes to all modes',
1516
1509
  rules: [{ toolName: 'tool1', decision: PolicyDecision.DENY }],
1510
+ allToolNames: ['tool1', 'tool2'],
1517
1511
  expected: ['tool1'],
1518
1512
  },
1519
1513
  {
@@ -1533,6 +1527,7 @@ describe('PolicyEngine', () => {
1533
1527
  modes: [ApprovalMode.DEFAULT],
1534
1528
  },
1535
1529
  ],
1530
+ allToolNames: ['tool1'],
1536
1531
  expected: [],
1537
1532
  },
1538
1533
  {
@@ -1549,6 +1544,7 @@ describe('PolicyEngine', () => {
1549
1544
  modes: [ApprovalMode.DEFAULT],
1550
1545
  },
1551
1546
  ],
1547
+ allToolNames: ['tool1', 'tool2', 'tool3'],
1552
1548
  expected: ['tool1'],
1553
1549
  },
1554
1550
  {
@@ -1567,6 +1563,7 @@ describe('PolicyEngine', () => {
1567
1563
  modes: [ApprovalMode.DEFAULT],
1568
1564
  },
1569
1565
  ],
1566
+ allToolNames: ['tool1'],
1570
1567
  expected: ['tool1'],
1571
1568
  },
1572
1569
  {
@@ -1585,6 +1582,7 @@ describe('PolicyEngine', () => {
1585
1582
  modes: [ApprovalMode.DEFAULT],
1586
1583
  },
1587
1584
  ],
1585
+ allToolNames: ['tool1'],
1588
1586
  expected: [],
1589
1587
  },
1590
1588
  {
@@ -1597,7 +1595,8 @@ describe('PolicyEngine', () => {
1597
1595
  },
1598
1596
  ],
1599
1597
  nonInteractive: true,
1600
- expected: [],
1598
+ allToolNames: ['tool1'],
1599
+ expected: ['tool1'],
1601
1600
  },
1602
1601
  {
1603
1602
  name: 'should ignore rules with argsPattern',
@@ -1609,6 +1608,7 @@ describe('PolicyEngine', () => {
1609
1608
  modes: [ApprovalMode.DEFAULT],
1610
1609
  },
1611
1610
  ],
1611
+ allToolNames: ['tool1'],
1612
1612
  expected: [],
1613
1613
  },
1614
1614
  {
@@ -1621,6 +1621,7 @@ describe('PolicyEngine', () => {
1621
1621
  },
1622
1622
  ],
1623
1623
  approvalMode: ApprovalMode.PLAN,
1624
+ allToolNames: ['tool1'],
1624
1625
  expected: ['tool1'],
1625
1626
  },
1626
1627
  {
@@ -1633,6 +1634,7 @@ describe('PolicyEngine', () => {
1633
1634
  },
1634
1635
  ],
1635
1636
  approvalMode: ApprovalMode.DEFAULT,
1637
+ allToolNames: ['tool1'],
1636
1638
  expected: [],
1637
1639
  },
1638
1640
  {
@@ -1651,36 +1653,55 @@ describe('PolicyEngine', () => {
1651
1653
  },
1652
1654
  ],
1653
1655
  approvalMode: ApprovalMode.YOLO,
1656
+ allToolNames: ['dangerous-tool', 'safe-tool'],
1654
1657
  expected: [],
1655
1658
  },
1656
1659
  {
1657
1660
  name: 'should respect server wildcard DENY',
1658
1661
  rules: [
1659
1662
  {
1660
- toolName: 'server__*',
1663
+ toolName: 'mcp_server_*',
1664
+ mcpName: 'server',
1661
1665
  decision: PolicyDecision.DENY,
1662
1666
  modes: [ApprovalMode.DEFAULT],
1663
1667
  },
1664
1668
  ],
1665
- expected: ['server__*'],
1669
+ allToolNames: [
1670
+ 'mcp_server_tool1',
1671
+ 'mcp_server_tool2',
1672
+ 'mcp_other_tool',
1673
+ ],
1674
+ metadata: new Map([
1675
+ ['mcp_server_tool1', { _serverName: 'server' }],
1676
+ ['mcp_server_tool2', { _serverName: 'server' }],
1677
+ ['mcp_other_tool', { _serverName: 'other' }],
1678
+ ]),
1679
+ expected: ['mcp_server_tool1', 'mcp_server_tool2'],
1666
1680
  },
1667
1681
  {
1668
1682
  name: 'should expand server wildcard for specific tools if already processed',
1669
1683
  rules: [
1670
1684
  {
1671
- toolName: 'server__*',
1685
+ toolName: 'mcp_server_*',
1686
+ mcpName: 'server',
1672
1687
  decision: PolicyDecision.DENY,
1673
1688
  priority: 100,
1674
1689
  modes: [ApprovalMode.DEFAULT],
1675
1690
  },
1676
1691
  {
1677
- toolName: 'server__tool1',
1678
- decision: PolicyDecision.DENY,
1692
+ toolName: 'mcp_server_tool1',
1693
+ mcpName: 'server',
1694
+ decision: PolicyDecision.DENY, // redundant but tests ordering
1679
1695
  priority: 10,
1680
1696
  modes: [ApprovalMode.DEFAULT],
1681
1697
  },
1682
1698
  ],
1683
- expected: ['server__*', 'server__tool1'],
1699
+ allToolNames: ['mcp_server_tool1', 'mcp_server_tool2'],
1700
+ metadata: new Map([
1701
+ ['mcp_server_tool1', { _serverName: 'server' }],
1702
+ ['mcp_server_tool2', { _serverName: 'server' }],
1703
+ ]),
1704
+ expected: ['mcp_server_tool1', 'mcp_server_tool2'],
1684
1705
  },
1685
1706
  {
1686
1707
  name: 'should exclude run_shell_command but NOT write_file in simulated Plan Mode',
@@ -1707,24 +1728,29 @@ describe('PolicyEngine', () => {
1707
1728
  priority: 10,
1708
1729
  },
1709
1730
  ],
1710
- expected: ['run_shell_command'],
1731
+ allToolNames: ['write_file', 'run_shell_command', 'read_file'],
1732
+ expected: ['run_shell_command', 'read_file'],
1711
1733
  },
1712
1734
  {
1713
1735
  name: 'should NOT exclude tool if covered by a higher priority wildcard ALLOW',
1714
1736
  rules: [
1715
1737
  {
1716
- toolName: 'server__*',
1738
+ toolName: 'mcp_server_*',
1739
+ mcpName: 'server',
1717
1740
  decision: PolicyDecision.ALLOW,
1718
1741
  priority: 100,
1719
1742
  modes: [ApprovalMode.DEFAULT],
1720
1743
  },
1721
1744
  {
1722
- toolName: 'server__tool1',
1745
+ toolName: 'mcp_server_tool1',
1746
+ mcpName: 'server',
1723
1747
  decision: PolicyDecision.DENY,
1724
1748
  priority: 10,
1725
1749
  modes: [ApprovalMode.DEFAULT],
1726
1750
  },
1727
1751
  ],
1752
+ allToolNames: ['mcp_server_tool1'],
1753
+ metadata: new Map([['mcp_server_tool1', { _serverName: 'server' }]]),
1728
1754
  expected: [],
1729
1755
  },
1730
1756
  {
@@ -1736,38 +1762,53 @@ describe('PolicyEngine', () => {
1736
1762
  priority: 10,
1737
1763
  },
1738
1764
  ],
1739
- expected: ['*'],
1765
+ allToolNames: ['toolA', 'toolB', 'mcp_server_toolC'],
1766
+ expected: ['toolA', 'toolB', 'mcp_server_toolC'], // all tools denied by *
1740
1767
  },
1741
1768
  {
1742
1769
  name: 'should handle MCP category wildcard *__* in getExcludedTools',
1743
1770
  rules: [
1744
1771
  {
1745
- toolName: '*__*',
1772
+ toolName: 'mcp_*',
1746
1773
  decision: PolicyDecision.DENY,
1747
1774
  priority: 10,
1748
1775
  },
1749
1776
  ],
1750
- expected: ['*__*'],
1777
+ allToolNames: ['localTool', 'mcp_myserver_mytool'],
1778
+ metadata: new Map([
1779
+ ['mcp_myserver_mytool', { _serverName: 'myserver' }],
1780
+ ]),
1781
+ expected: ['mcp_myserver_mytool'],
1751
1782
  },
1752
1783
  {
1753
- name: 'should handle tool wildcard *__search in getExcludedTools',
1784
+ name: 'should handle tool wildcard mcp_server_* in getExcludedTools',
1754
1785
  rules: [
1755
1786
  {
1756
- toolName: '*__search',
1787
+ toolName: 'mcp_server_*',
1757
1788
  decision: PolicyDecision.DENY,
1758
1789
  priority: 10,
1759
1790
  },
1760
1791
  ],
1761
- expected: ['*__search'],
1792
+ allToolNames: [
1793
+ 'localTool',
1794
+ 'mcp_server_search',
1795
+ 'mcp_otherserver_read',
1796
+ ],
1797
+ metadata: new Map([
1798
+ ['mcp_server_search', { _serverName: 'server' }],
1799
+ ['mcp_otherserver_read', { _serverName: 'otherserver' }],
1800
+ ]),
1801
+ expected: ['mcp_server_search'],
1762
1802
  },
1763
1803
  ];
1764
- it.each(testCases)('$name', ({ rules, approvalMode, nonInteractive, expected }) => {
1804
+ it.each(testCases)('$name', ({ rules, approvalMode, nonInteractive, allToolNames, metadata, expected, }) => {
1765
1805
  engine = new PolicyEngine({
1766
1806
  rules,
1767
1807
  approvalMode: approvalMode ?? ApprovalMode.DEFAULT,
1768
1808
  nonInteractive: nonInteractive ?? false,
1769
1809
  });
1770
- const excluded = engine.getExcludedTools();
1810
+ const toolsSet = allToolNames ? new Set(allToolNames) : undefined;
1811
+ const excluded = engine.getExcludedTools(metadata, toolsSet);
1771
1812
  expect(Array.from(excluded).sort()).toEqual(expected.sort());
1772
1813
  });
1773
1814
  it('should skip annotation-based rules when no metadata is provided', () => {
@@ -1780,7 +1821,7 @@ describe('PolicyEngine', () => {
1780
1821
  },
1781
1822
  ],
1782
1823
  });
1783
- const excluded = engine.getExcludedTools();
1824
+ const excluded = engine.getExcludedTools(undefined, new Set(['dangerous_tool']));
1784
1825
  expect(Array.from(excluded)).toEqual([]);
1785
1826
  });
1786
1827
  it('should exclude tools matching annotation-based DENY rule when metadata is provided', () => {
@@ -1797,7 +1838,7 @@ describe('PolicyEngine', () => {
1797
1838
  ['dangerous_tool', { destructiveHint: true }],
1798
1839
  ['safe_tool', { readOnlyHint: true }],
1799
1840
  ]);
1800
- const excluded = engine.getExcludedTools(metadata);
1841
+ const excluded = engine.getExcludedTools(metadata, new Set(['dangerous_tool', 'safe_tool']));
1801
1842
  expect(Array.from(excluded)).toEqual(['dangerous_tool']);
1802
1843
  });
1803
1844
  it('should NOT exclude tools whose annotations do not match', () => {
@@ -1813,14 +1854,15 @@ describe('PolicyEngine', () => {
1813
1854
  const metadata = new Map([
1814
1855
  ['safe_tool', { readOnlyHint: true }],
1815
1856
  ]);
1816
- const excluded = engine.getExcludedTools(metadata);
1857
+ const excluded = engine.getExcludedTools(metadata, new Set(['safe_tool']));
1817
1858
  expect(Array.from(excluded)).toEqual([]);
1818
1859
  });
1819
1860
  it('should exclude tools matching both toolName pattern AND annotations', () => {
1820
1861
  engine = new PolicyEngine({
1821
1862
  rules: [
1822
1863
  {
1823
- toolName: 'server__*',
1864
+ toolName: 'mcp_server_*',
1865
+ mcpName: 'server',
1824
1866
  toolAnnotations: { destructiveHint: true },
1825
1867
  decision: PolicyDecision.DENY,
1826
1868
  priority: 10,
@@ -1828,12 +1870,22 @@ describe('PolicyEngine', () => {
1828
1870
  ],
1829
1871
  });
1830
1872
  const metadata = new Map([
1831
- ['server__dangerous_tool', { destructiveHint: true }],
1832
- ['other__dangerous_tool', { destructiveHint: true }],
1833
- ['server__safe_tool', { readOnlyHint: true }],
1873
+ [
1874
+ 'mcp_server_dangerous_tool',
1875
+ { destructiveHint: true, _serverName: 'server' },
1876
+ ],
1877
+ [
1878
+ 'mcp_other_dangerous_tool',
1879
+ { destructiveHint: true, _serverName: 'other' },
1880
+ ],
1881
+ ['mcp_server_safe_tool', { readOnlyHint: true, _serverName: 'server' }],
1834
1882
  ]);
1835
- const excluded = engine.getExcludedTools(metadata);
1836
- expect(Array.from(excluded)).toEqual(['server__dangerous_tool']);
1883
+ const excluded = engine.getExcludedTools(metadata, new Set([
1884
+ 'mcp_server_dangerous_tool',
1885
+ 'mcp_other_dangerous_tool',
1886
+ 'mcp_server_safe_tool',
1887
+ ]));
1888
+ expect(Array.from(excluded)).toEqual(['mcp_server_dangerous_tool']);
1837
1889
  });
1838
1890
  it('should exclude unprocessed tools from allToolNames when global DENY is active', () => {
1839
1891
  engine = new PolicyEngine({
@@ -1849,8 +1901,8 @@ describe('PolicyEngine', () => {
1849
1901
  priority: 70,
1850
1902
  },
1851
1903
  {
1852
- // Simulates plan.toml: mcpName="*" → toolName="*__*"
1853
- toolName: '*__*',
1904
+ // Simulates plan.toml: mcpName="*" → toolName="mcp_*"
1905
+ toolName: 'mcp_*',
1854
1906
  toolAnnotations: { readOnlyHint: true },
1855
1907
  decision: PolicyDecision.ASK_USER,
1856
1908
  priority: 70,
@@ -1861,35 +1913,41 @@ describe('PolicyEngine', () => {
1861
1913
  },
1862
1914
  ],
1863
1915
  });
1864
- // MCP tools are registered with unqualified names in ToolRegistry
1916
+ // MCP tools are registered with qualified names in ToolRegistry
1865
1917
  const allToolNames = new Set([
1866
1918
  'glob',
1867
1919
  'read_file',
1868
1920
  'shell',
1869
1921
  'web_fetch',
1870
- 'read_mcp_tool',
1871
- 'write_mcp_tool',
1922
+ 'mcp_my-server_read_mcp_tool',
1923
+ 'mcp_my-server_write_mcp_tool',
1872
1924
  ]);
1873
1925
  // buildToolMetadata() includes _serverName for MCP tools
1874
1926
  const toolMetadata = new Map([
1875
- ['read_mcp_tool', { readOnlyHint: true, _serverName: 'my-server' }],
1876
- ['write_mcp_tool', { readOnlyHint: false, _serverName: 'my-server' }],
1927
+ [
1928
+ 'mcp_my-server_read_mcp_tool',
1929
+ { readOnlyHint: true, _serverName: 'my-server' },
1930
+ ],
1931
+ [
1932
+ 'mcp_my-server_write_mcp_tool',
1933
+ { readOnlyHint: false, _serverName: 'my-server' },
1934
+ ],
1877
1935
  ]);
1878
1936
  const excluded = engine.getExcludedTools(toolMetadata, allToolNames);
1879
1937
  expect(excluded.has('shell')).toBe(true);
1880
1938
  expect(excluded.has('web_fetch')).toBe(true);
1881
1939
  // Non-read-only MCP tool excluded by catch-all DENY
1882
- expect(excluded.has('write_mcp_tool')).toBe(true);
1940
+ expect(excluded.has('mcp_my-server_write_mcp_tool')).toBe(true);
1883
1941
  expect(excluded.has('glob')).toBe(false);
1884
1942
  expect(excluded.has('read_file')).toBe(false);
1885
1943
  // Read-only MCP tool allowed by annotation rule
1886
- expect(excluded.has('read_mcp_tool')).toBe(false);
1944
+ expect(excluded.has('mcp_my-server_read_mcp_tool')).toBe(false);
1887
1945
  });
1888
- it('should match already-qualified MCP tool names without _serverName', () => {
1946
+ it('should match MCP wildcard rules when explicitly mapped with _serverName', () => {
1889
1947
  engine = new PolicyEngine({
1890
1948
  rules: [
1891
1949
  {
1892
- toolName: '*__*',
1950
+ toolName: 'mcp_*',
1893
1951
  toolAnnotations: { readOnlyHint: true },
1894
1952
  decision: PolicyDecision.ASK_USER,
1895
1953
  priority: 70,
@@ -1902,17 +1960,23 @@ describe('PolicyEngine', () => {
1902
1960
  });
1903
1961
  // Tool registered with qualified name (collision case)
1904
1962
  const allToolNames = new Set([
1905
- 'myserver__read_tool',
1906
- 'myserver__write_tool',
1963
+ 'mcp_myserver_read_tool',
1964
+ 'mcp_myserver_write_tool',
1907
1965
  ]);
1908
1966
  const toolMetadata = new Map([
1909
- ['myserver__read_tool', { readOnlyHint: true }],
1910
- ['myserver__write_tool', { readOnlyHint: false }],
1967
+ [
1968
+ 'mcp_myserver_read_tool',
1969
+ { readOnlyHint: true, _serverName: 'myserver' },
1970
+ ],
1971
+ [
1972
+ 'mcp_myserver_write_tool',
1973
+ { readOnlyHint: false, _serverName: 'myserver' },
1974
+ ],
1911
1975
  ]);
1912
1976
  const excluded = engine.getExcludedTools(toolMetadata, allToolNames);
1913
- // Qualified name already contains __, matched directly without _serverName
1914
- expect(excluded.has('myserver__read_tool')).toBe(false);
1915
- expect(excluded.has('myserver__write_tool')).toBe(true);
1977
+ // Qualified name matched using explicit _serverName
1978
+ expect(excluded.has('mcp_myserver_read_tool')).toBe(false);
1979
+ expect(excluded.has('mcp_myserver_write_tool')).toBe(true);
1916
1980
  });
1917
1981
  it('should not exclude unprocessed tools when allToolNames is not provided (backward compat)', () => {
1918
1982
  engine = new PolicyEngine({
@@ -2001,7 +2065,7 @@ describe('PolicyEngine', () => {
2001
2065
  modes: [ApprovalMode.PLAN],
2002
2066
  },
2003
2067
  {
2004
- toolName: '*__*',
2068
+ toolName: 'mcp_*',
2005
2069
  toolAnnotations: { readOnlyHint: true },
2006
2070
  decision: PolicyDecision.ASK_USER,
2007
2071
  priority: 70,
@@ -2032,13 +2096,19 @@ describe('PolicyEngine', () => {
2032
2096
  'write_todos',
2033
2097
  'memory',
2034
2098
  'save_memory',
2035
- 'read_tool',
2036
- 'write_tool',
2099
+ 'mcp_mcp-server_read_tool',
2100
+ 'mcp_mcp-server_write_tool',
2037
2101
  ]);
2038
2102
  // buildToolMetadata() includes _serverName for MCP tools
2039
2103
  const toolMetadata = new Map([
2040
- ['read_tool', { readOnlyHint: true, _serverName: 'mcp-server' }],
2041
- ['write_tool', { readOnlyHint: false, _serverName: 'mcp-server' }],
2104
+ [
2105
+ 'mcp_mcp-server_read_tool',
2106
+ { readOnlyHint: true, _serverName: 'mcp-server' },
2107
+ ],
2108
+ [
2109
+ 'mcp_mcp-server_write_tool',
2110
+ { readOnlyHint: false, _serverName: 'mcp-server' },
2111
+ ],
2042
2112
  ]);
2043
2113
  const excluded = engine.getExcludedTools(toolMetadata, allToolNames);
2044
2114
  // These should be excluded (caught by catch-all DENY)
@@ -2051,7 +2121,7 @@ describe('PolicyEngine', () => {
2051
2121
  expect(excluded.has('write_file')).toBe(true);
2052
2122
  expect(excluded.has('replace')).toBe(true);
2053
2123
  // Non-read-only MCP tool excluded by catch-all DENY
2054
- expect(excluded.has('write_tool')).toBe(true);
2124
+ expect(excluded.has('mcp_mcp-server_write_tool')).toBe(true);
2055
2125
  // These should NOT be excluded (explicitly allowed)
2056
2126
  expect(excluded.has('glob')).toBe(false);
2057
2127
  expect(excluded.has('grep_search')).toBe(false);
@@ -2063,7 +2133,7 @@ describe('PolicyEngine', () => {
2063
2133
  expect(excluded.has('exit_plan_mode')).toBe(false);
2064
2134
  expect(excluded.has('save_memory')).toBe(false);
2065
2135
  // Read-only MCP tool allowed by annotation rule (matched via _serverName)
2066
- expect(excluded.has('read_tool')).toBe(false);
2136
+ expect(excluded.has('mcp_mcp-server_read_tool')).toBe(false);
2067
2137
  });
2068
2138
  });
2069
2139
  describe('YOLO mode with ask_user tool', () => {
@@ -2339,22 +2409,22 @@ describe('PolicyEngine', () => {
2339
2409
  engine = new PolicyEngine({
2340
2410
  rules: [
2341
2411
  {
2342
- toolName: '*__*',
2412
+ toolName: 'mcp_*',
2343
2413
  toolAnnotations: { experimental: true },
2344
2414
  decision: PolicyDecision.DENY,
2345
2415
  priority: 20,
2346
2416
  },
2347
2417
  {
2348
- toolName: '*__*',
2418
+ toolName: 'mcp_*',
2349
2419
  decision: PolicyDecision.ALLOW,
2350
2420
  priority: 10,
2351
2421
  },
2352
2422
  ],
2353
2423
  });
2354
- expect((await engine.check({ name: 'mcp__test' }, 'mcp', {
2424
+ expect((await engine.check({ name: 'mcp_mcp_test' }, 'mcp', {
2355
2425
  experimental: true,
2356
2426
  })).decision).toBe(PolicyDecision.DENY);
2357
- expect((await engine.check({ name: 'mcp__stable' }, 'mcp', {
2427
+ expect((await engine.check({ name: 'mcp_mcp_stable' }, 'mcp', {
2358
2428
  experimental: false,
2359
2429
  })).decision).toBe(PolicyDecision.ALLOW);
2360
2430
  });