Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions docs/reference/policy-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ primary conditions are the tool's name and its arguments.

The `toolName` in the rule must match the name of the tool being called.

- **Wildcards**: For Model-hosting-protocol (MCP) servers, you can use a
wildcard. A `toolName` of `my-server__*` will match any tool from the
`my-server` MCP.
- **Wildcards**: You can use wildcards to match multiple tools.
- `*`: Matches **any tool** (built-in or MCP).
- `server__*`: Matches any tool from a specific MCP server.
- `*__toolName`: Matches a specific tool name across **all** MCP servers.
- `*__*`: Matches **any tool from any MCP server**.

#### Arguments pattern

Expand Down Expand Up @@ -144,9 +146,9 @@ A rule matches a tool call if all of its conditions are met:

1. **Tool name**: The `toolName` in the rule must match the name of the tool
being called.
- **Wildcards**: For Model-hosting-protocol (MCP) servers, you can use a
wildcard. A `toolName` of `my-server__*` will match any tool from the
`my-server` MCP.
- **Wildcards**: You can use wildcards like `*`, `server__*`, or
`*__toolName` to match multiple tools. See [Tool Name](#tool-name) for
details.
2. **Arguments pattern**: If `argsPattern` is specified, the tool's arguments
are converted to a stable JSON string, which is then tested against the
provided regular expression. If the arguments don't match the pattern, the
Expand Down Expand Up @@ -272,13 +274,12 @@ priority = 100

### Special syntax for MCP tools

You can create rules that target tools from Model-hosting-protocol (MCP) servers
using the `mcpName` field or a wildcard pattern.
You can create rules that target tools from Model Context Protocol (MCP) servers
using the `mcpName` field or composite wildcard patterns.

**1. Using `mcpName`**
**1. Targeting a specific tool on a server**

To target a specific tool from a specific server, combine `mcpName` and
`toolName`.
Combine `mcpName` and `toolName` to target a single operation.

```toml
# Allows the `search` tool on the `my-jira-server` MCP
Expand All @@ -289,10 +290,10 @@ decision = "allow"
priority = 200
```

**2. Using a wildcard**
**2. Targeting all tools on a specific server**

To create a rule that applies to _all_ tools on a specific MCP server, specify
only the `mcpName`.
Specify only the `mcpName` to apply a rule to every tool provided by that
server.

```toml
# Denies all tools from the `untrusted-server` MCP
Expand All @@ -303,6 +304,33 @@ priority = 500
deny_message = "This server is not trusted by the admin."
```

**3. Targeting all MCP servers**

Use `mcpName = "*"` to create a rule that applies to **all** tools from **any**
registered MCP server. This is useful for setting category-wide defaults.

```toml
# Ask user for any tool call from any MCP server
[[rule]]
mcpName = "*"
decision = "ask_user"
priority = 10
```

**4. Targeting a tool name across all servers**

Use `mcpName = "*"` with a specific `toolName` to target that operation
regardless of which server provides it.

```toml
# Allow the `search` tool across all connected MCP servers
[[rule]]
mcpName = "*"
toolName = "search"
decision = "allow"
priority = 50
```

## Default policies

The Gemini CLI ships with a set of default policies to provide a safe
Expand Down
29 changes: 29 additions & 0 deletions packages/cli/src/config/policy-engine.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,35 @@ describe('Policy Engine Integration Tests', () => {
).toBe(PolicyDecision.ASK_USER);
});

it('should handle global MCP wildcard (*) in settings', async () => {
const settings: Settings = {
mcp: {
allowed: ['*'],
},
};

const config = await createPolicyEngineConfig(
settings,
ApprovalMode.DEFAULT,
);
const engine = new PolicyEngine(config);

// ANY tool with a server name should be allowed
expect(
(await engine.check({ name: 'mcp-server__tool' }, 'mcp-server'))
.decision,
).toBe(PolicyDecision.ALLOW);
expect(
(await engine.check({ name: 'another-server__tool' }, 'another-server'))
.decision,
).toBe(PolicyDecision.ALLOW);

// Built-in tools should NOT be allowed by the MCP wildcard
expect(
(await engine.check({ name: 'run_shell_command' }, undefined)).decision,
).toBe(PolicyDecision.ASK_USER);
});

it('should correctly prioritize specific tool excludes over MCP server wildcards', async () => {
const settings: Settings = {
mcp: {
Expand Down
117 changes: 108 additions & 9 deletions packages/core/src/policy/policy-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,63 @@ describe('PolicyEngine', () => {
});

describe('MCP server wildcard patterns', () => {
it('should match global wildcard (*)', async () => {
engine = new PolicyEngine({
rules: [
{ toolName: '*', decision: PolicyDecision.ALLOW, priority: 10 },
],
});

expect(
(await engine.check({ name: 'read_file' }, undefined)).decision,
).toBe(PolicyDecision.ALLOW);
expect(
(await engine.check({ name: 'my-server__tool' }, 'my-server')).decision,
).toBe(PolicyDecision.ALLOW);
});

it('should match any MCP tool when toolName is *__*', async () => {
engine = new PolicyEngine({
rules: [
{ toolName: '*__*', decision: PolicyDecision.ALLOW, priority: 10 },
],
defaultDecision: PolicyDecision.DENY,
});

expect((await engine.check({ name: 'mcp__tool' }, 'mcp')).decision).toBe(
PolicyDecision.ALLOW,
);
expect(
(await engine.check({ name: 'other__tool' }, 'other')).decision,
).toBe(PolicyDecision.ALLOW);
expect(
(await engine.check({ name: 'read_file' }, undefined)).decision,
).toBe(PolicyDecision.DENY);
});

it('should match specific tool across all servers when using *__tool', async () => {
engine = new PolicyEngine({
rules: [
{
toolName: '*__search',
decision: PolicyDecision.ALLOW,
priority: 10,
},
],
defaultDecision: PolicyDecision.DENY,
});

expect((await engine.check({ name: 'ws__search' }, 'ws')).decision).toBe(
PolicyDecision.ALLOW,
);
expect((await engine.check({ name: 'gh__search' }, 'gh')).decision).toBe(
PolicyDecision.ALLOW,
);
expect((await engine.check({ name: 'gh__list' }, 'gh')).decision).toBe(
PolicyDecision.DENY,
);
});

it('should match MCP server wildcard patterns', async () => {
const rules: PolicyRule[] = [
{
Expand All @@ -449,26 +506,35 @@ describe('PolicyEngine', () => {

// Should match my-server tools
expect(
(await engine.check({ name: 'my-server__tool1' }, undefined)).decision,
(await engine.check({ name: 'my-server__tool1' }, 'my-server'))
.decision,
).toBe(PolicyDecision.ALLOW);
expect(
(await engine.check({ name: 'my-server__another_tool' }, undefined))
(await engine.check({ name: 'my-server__another_tool' }, 'my-server'))
.decision,
).toBe(PolicyDecision.ALLOW);

// Should match blocked-server tools
expect(
(await engine.check({ name: 'blocked-server__tool1' }, undefined))
.decision,
(
await engine.check(
{ name: 'blocked-server__tool1' },
'blocked-server',
)
).decision,
).toBe(PolicyDecision.DENY);
expect(
(await engine.check({ name: 'blocked-server__dangerous' }, undefined))
.decision,
(
await engine.check(
{ name: 'blocked-server__dangerous' },
'blocked-server',
)
).decision,
).toBe(PolicyDecision.DENY);

// Should not match other patterns
expect(
(await engine.check({ name: 'other-server__tool' }, undefined))
(await engine.check({ name: 'other-server__tool' }, 'other-server'))
.decision,
).toBe(PolicyDecision.ASK_USER);
expect(
Expand Down Expand Up @@ -497,11 +563,11 @@ describe('PolicyEngine', () => {

// Specific tool deny should override server allow
expect(
(await engine.check({ name: 'my-server__dangerous-tool' }, undefined))
(await engine.check({ name: 'my-server__dangerous-tool' }, 'my-server'))
.decision,
).toBe(PolicyDecision.DENY);
expect(
(await engine.check({ name: 'my-server__safe-tool' }, undefined))
(await engine.check({ name: 'my-server__safe-tool' }, 'my-server'))
.decision,
).toBe(PolicyDecision.ALLOW);
});
Expand Down Expand Up @@ -2262,6 +2328,39 @@ describe('PolicyEngine', () => {
],
expected: [],
},
{
name: 'should handle global wildcard * in getExcludedTools',
rules: [
{
toolName: '*',
decision: PolicyDecision.DENY,
priority: 10,
},
],
expected: ['*'],
},
{
name: 'should handle MCP category wildcard *__* in getExcludedTools',
rules: [
{
toolName: '*__*',
decision: PolicyDecision.DENY,
priority: 10,
},
],
expected: ['*__*'],
},
{
name: 'should handle tool wildcard *__search in getExcludedTools',
rules: [
{
toolName: '*__search',
decision: PolicyDecision.DENY,
priority: 10,
},
],
expected: ['*__search'],
},
];

it.each(testCases)(
Expand Down
Loading
Loading