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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- **No false warning when combining `allowedTools` and `disallowedTools`; tool permission docs corrected** ([#212](https://github.com/ben-vargas/ai-sdk-provider-claude-code/issues/212)) - Validation warned "Both allowedTools and disallowedTools are specified. Only allowedTools will be used." That was never true: the provider forwards both lists unchanged and the CLI applies both, with matching deny rules taking precedence. The warning is removed (forwarding is unchanged). The README, `ClaudeCodeSettings` JSDoc, guides, and `tool-management.ts` example no longer say the lists can't be combined or that `allowedTools` is an allowlist. `allowedTools` only pre-approves tools (they run without prompting); use `tools` to restrict which built-in tools exist (`tools: []` disables them all), and control MCP tools separately.

## [3.7.1] - 2026-10-25

### Changed
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,22 @@ const model = claudeCode('sonnet', {

The `OnUserDialog`, `UserDialogRequest`, and `UserDialogResult` types are re-exported. Note that `UserDialogResult.result` is typed `unknown` — the CLI validates it against the dialog kind's own result schema at runtime, and a result that doesn't match (e.g. the wrong shape or an unknown string) is **silently** replaced by the dialog's default (for `'refusal_fallback_prompt'`, `'cancelled'`), so double-check the result values for each kind you handle.

### Tool availability and pre-approval (`tools`, `allowedTools`, `disallowedTools`)

These three settings do different jobs, and you can combine them:

- `tools` sets which built-in tools exist (`['Read', 'Grep']`; `[]` disables all built-in tools). MCP tools are controlled separately, through `mcpServers` and permission rules.
- `allowedTools` only **pre-approves** tools, so they run without a permission prompt. It is not an allowlist: unlisted tools stay available and go through `permissionMode`, `canUseTool`, and hooks as usual.
- `disallowedTools` denies tools (or matching calls, such as `'Bash(rm:*)'`). Deny rules take precedence, so a tool listed in both lists is denied.

```ts
const model = claudeCode('sonnet', {
tools: ['Read', 'Grep', 'Bash'], // only these built-in tools exist
allowedTools: ['Read', 'Grep', 'Bash(git log:*)'], // run without prompting
disallowedTools: ['Bash(rm:*)'], // calls matching this rule are always denied
});
```

### Permission decisions (`canUseTool` extras)

SDK 0.3.x enriched the `canUseTool` callback (no provider change needed — these arrive on the existing `options` argument):
Expand Down
77 changes: 56 additions & 21 deletions docs/ai-sdk-v4/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ const { text } = await generateText({
| `maxTurns` | `number` | `undefined` | Maximum conversation turns |
| `maxThinkingTokens` | `number` | `undefined` | Maximum thinking tokens |
| `permissionMode` | `string` | `'default'` | Permission mode for tools |
| `allowedTools` | `string[]` | `undefined` | Tools to explicitly allow |
| `disallowedTools` | `string[]` | `undefined` | Tools to restrict |
| `allowedTools` | `string[]` | `undefined` | Tools to pre-approve |
| `disallowedTools` | `string[]` | `undefined` | Tools to deny (deny wins) |
| `mcpServers` | `object` | `undefined` | MCP server configuration |
| `resume` | `string` | `undefined` | Resume an existing session |

Expand Down Expand Up @@ -232,71 +232,105 @@ Logger options:

### Tool Management

Control which tools Claude Code can use with either `allowedTools` (allowlist) or `disallowedTools` (denylist). These flags work for **both built-in Claude tools and MCP tools**, providing session-only permission overrides.
Control tool permissions with `allowedTools` and `disallowedTools`. The two lists can be combined. These flags work for **both built-in Claude tools and MCP tools**, providing session-only permission rules.

- `allowedTools` **pre-approves** tools: matching calls run without a permission prompt. It is not an allowlist; unlisted tools stay available and follow normal permissions.
- `disallowedTools` **denies** tools. Deny rules take precedence, so a tool in both lists is denied.

To restrict which built-in tools exist, current provider versions offer a separate `tools` setting (see the [main README](../../README.md)); MCP tools are controlled separately. In these legacy versions, use `disallowedTools` to remove tools.

#### Tool Types

- **Built-in tools**: `Bash`, `Edit`, `Read`, `Write`, `LS`, `Grep`, etc.
- **MCP tools**: `mcp__serverName__toolName` format

#### Using allowedTools (Allowlist)
#### Pre-approving tools with allowedTools

```typescript
import { createClaudeCode } from 'ai-sdk-provider-claude-code';

// Only allow specific built-in tools
// Pre-approve read-only tools (other tools still follow normal permissions)
const readOnlyClaude = createClaudeCode({
allowedTools: ['Read', 'LS', 'Grep'],
defaultSettings: {
allowedTools: ['Read', 'LS', 'Grep'],
},
});

// Allow specific Bash commands using specifiers
// Pre-approve specific Bash commands using specifiers
const gitOnlyClaude = createClaudeCode({
allowedTools: ['Bash(git log:*)', 'Bash(git diff:*)', 'Bash(git status)'],
defaultSettings: {
allowedTools: ['Bash(git log:*)', 'Bash(git diff:*)', 'Bash(git status)'],
},
});

// Mix built-in and MCP tools
const mixedClaude = createClaudeCode({
allowedTools: ['Read', 'Bash(npm test:*)', 'mcp__filesystem__read_text_file', 'mcp__git__status'],
defaultSettings: {
allowedTools: [
'Read',
'Bash(npm test:*)',
'mcp__filesystem__read_text_file',
'mcp__git__status',
],
},
});
```

#### Using disallowedTools (Denylist)
#### Denying tools with disallowedTools

```typescript
// Block dangerous operations
const safeClaude = createClaudeCode({
disallowedTools: ['Write', 'Edit', 'Delete', 'Bash(rm:*)', 'Bash(sudo:*)'],
defaultSettings: {
disallowedTools: ['Write', 'Edit', 'Delete', 'Bash(rm:*)', 'Bash(sudo:*)'],
},
});

// Block all Bash and MCP write operations
const restrictedClaude = createClaudeCode({
disallowedTools: ['Bash', 'mcp__filesystem__write_file', 'mcp__git__commit', 'mcp__git__push'],
defaultSettings: {
disallowedTools: ['Bash', 'mcp__filesystem__write_file', 'mcp__git__commit', 'mcp__git__push'],
},
});
```

#### Combining allowedTools and disallowedTools

```typescript
// Pre-approve Read and Bash, except Bash calls that match the deny rule
const combinedClaude = createClaudeCode({
defaultSettings: {
allowedTools: ['Read', 'Bash'],
disallowedTools: ['Bash(rm:*)'], // denies calls matching this rule
},
});
```

#### Model-Specific Overrides

```typescript
const baseClaude = createClaudeCode({
disallowedTools: ['Write', 'Edit'],
defaultSettings: {
disallowedTools: ['Write', 'Edit'],
},
});

// Override for a specific call
const { text } = await generateText({
model: baseClaude('opus', {
disallowedTools: [], // Allow everything for this call
disallowedTools: [], // Clear the provider-level deny list for this call
}),
prompt: 'Create a simple config file...',
});
```

**Key Points**:

- These are **session-only permission overrides** (same syntax as settings.json)
- Higher priority than settings files
- These are **session-only permission rules** (same syntax as settings.json), added to the rules from your settings files
- Deny rules take precedence over allow rules, so `allowedTools` cannot override a matching deny rule
- Works for both built-in tools AND MCP tools
- Cannot use both `allowedTools` and `disallowedTools` together
- Empty `allowedTools: []` = Explicit empty allowlist (no tools allowed)
- `allowedTools` and `disallowedTools` can be used together
- Empty `allowedTools: []` = no tools pre-approved (not "no tools allowed")
- Omitting the flags entirely = Falls back to normal permission system
- Use `/permissions` in Claude to see all available tool names

Expand All @@ -311,9 +345,10 @@ const { text } = await generateText({
| Configuration | CLI Flag Behavior | Result |
|--------------|-------------------|---------|
| No config | No `--allowedTools` or `--disallowedTools` | Falls back to settings.json and interactive prompts |
| `allowedTools: []` | `--allowedTools` (empty) | Explicit empty allowlist - blocks all tools |
| `allowedTools: ['Read']` | `--allowedTools Read` | Only allows Read tool |
| `disallowedTools: []` | `--disallowedTools` (empty) | No effect - normal permissions apply |
| `allowedTools: []` | No flag (empty lists are not passed) | No tools pre-approved - normal permissions apply |
| `allowedTools: ['Read']` | `--allowedTools Read` | Read runs without prompting; other tools follow normal permissions |
| `allowedTools: ['Bash', 'Read']` + `disallowedTools: ['Read']` | `--allowedTools Bash,Read --disallowedTools Read` | Read is denied (deny wins); Bash runs without prompting |
| `disallowedTools: []` | No flag (empty lists are not passed) | No effect - normal permissions apply |
| `disallowedTools: ['Write']` | `--disallowedTools Write` | Blocks Write tool, others follow normal permissions |

## Advanced Configuration
Expand Down
98 changes: 76 additions & 22 deletions docs/ai-sdk-v5/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ If you abort with a reason (`controller.abort(new Error('...'))`), `generateText
| `maxTurns` | `number` | `undefined` | Maximum conversation turns |
| `maxThinkingTokens` | `number` | `undefined` | Maximum thinking tokens |
| `permissionMode` | `string` | `'default'` | Permission mode for tools |
| `allowedTools` | `string[]` | `undefined` | Tools to explicitly allow |
| `disallowedTools` | `string[]` | `undefined` | Tools to restrict |
| `allowedTools` | `string[]` | `undefined` | Tools to pre-approve (run without prompting); not an allowlist |
| `disallowedTools` | `string[]` | `undefined` | Tools to deny; takes precedence over `allowedTools` |
| `mcpServers` | `object` | `undefined` | MCP server configuration |
| `env` | `Record<string, string>` | `undefined` | Environment variables passed to CLI |
| `resume` | `string` | `undefined` | Resume an existing session |
Expand Down Expand Up @@ -458,71 +458,122 @@ const model = claudeCode('sonnet', {

### Tool Management

Control which tools Claude Code can use with either `allowedTools` (allowlist) or `disallowedTools` (denylist). These flags work for **both built-in Claude tools and MCP tools**, providing session-only permission overrides.
Three settings control tools, and they can be combined:

- `tools` sets which **built-in** tools exist (`[]` disables all built-in tools). MCP tools are controlled separately, through `mcpServers` and permission rules.
- `allowedTools` **pre-approves** tools: matching calls run without a permission prompt. It is not an allowlist. Unlisted tools stay available and go through `permissionMode`, `canUseTool`, and hooks as usual.
- `disallowedTools` **denies** tools. A bare tool name removes the tool; a rule with a specifier, such as `Bash(rm:*)`, denies only matching calls. Deny rules take precedence over allow rules.

`allowedTools` and `disallowedTools` work for **both built-in Claude tools and MCP tools**, providing session-only permission rules.

#### Tool Types

- **Built-in tools**: `Bash`, `Edit`, `Read`, `Write`, `LS`, `Grep`, etc.
- **MCP tools**: `mcp__serverName__toolName` format

#### Using allowedTools (Allowlist)
#### Restricting built-in tools with tools

```typescript
import { createClaudeCode } from 'ai-sdk-provider-claude-code';

// Only allow specific built-in tools
// Only these built-in tools exist in the session
const readOnlyClaude = createClaudeCode({
allowedTools: ['Read', 'LS', 'Grep'],
defaultSettings: {
tools: ['Read', 'LS', 'Grep'],
},
});

// No built-in tools at all (MCP tools are unaffected)
const noBuiltinsClaude = createClaudeCode({
defaultSettings: {
tools: [],
},
});
```

#### Pre-approving tools with allowedTools

// Allow specific Bash commands using specifiers
const gitOnlyClaude = createClaudeCode({
allowedTools: ['Bash(git log:*)', 'Bash(git diff:*)', 'Bash(git status)'],
```typescript
// Run these without prompting; other tools still follow normal permissions
const gitClaude = createClaudeCode({
defaultSettings: {
allowedTools: ['Bash(git log:*)', 'Bash(git diff:*)', 'Bash(git status)'],
},
});

// Mix built-in and MCP tools
const mixedClaude = createClaudeCode({
allowedTools: ['Read', 'Bash(npm test:*)', 'mcp__filesystem__read_text_file', 'mcp__git__status'],
defaultSettings: {
allowedTools: [
'Read',
'Bash(npm test:*)',
'mcp__filesystem__read_text_file',
'mcp__git__status',
],
},
});
```

#### Using disallowedTools (Denylist)
#### Denying tools with disallowedTools

```typescript
// Block dangerous operations
const safeClaude = createClaudeCode({
disallowedTools: ['Write', 'Edit', 'Delete', 'Bash(rm:*)', 'Bash(sudo:*)'],
defaultSettings: {
disallowedTools: ['Write', 'Edit', 'Delete', 'Bash(rm:*)', 'Bash(sudo:*)'],
},
});

// Block all Bash and MCP write operations
const restrictedClaude = createClaudeCode({
disallowedTools: ['Bash', 'mcp__filesystem__write_file', 'mcp__git__commit', 'mcp__git__push'],
defaultSettings: {
disallowedTools: ['Bash', 'mcp__filesystem__write_file', 'mcp__git__commit', 'mcp__git__push'],
},
});
```

#### Combining allowedTools and disallowedTools

```typescript
// Pre-approve Read and Bash, except Bash calls that match the deny rule
const combinedClaude = createClaudeCode({
defaultSettings: {
allowedTools: ['Read', 'Bash'],
disallowedTools: ['Bash(rm:*)'], // denies calls matching this rule
},
});
```

Scoped Bash rules match the command as written, so `Bash(rm:*)` does not match forms such as `/bin/rm` or `bash -c 'rm ...'`; those still fall under the broad `Bash` pre-approval. For a hard boundary, deny the whole tool (`disallowedTools: ['Bash']`) or use sandboxing.

A tool that appears in both lists is denied: `allowedTools: ['Bash', 'Read']` with `disallowedTools: ['Read']` removes Read, while Bash stays pre-approved.

#### Model-Specific Overrides

```typescript
const baseClaude = createClaudeCode({
disallowedTools: ['Write', 'Edit'],
defaultSettings: {
disallowedTools: ['Write', 'Edit'],
},
});

// Override for a specific call
const result = await generateText({
model: baseClaude('opus', {
disallowedTools: [], // Allow everything for this call
disallowedTools: [], // Clear the provider-level deny list for this call
}),
prompt: 'Create a simple config file...',
});
```

**Key Points**:

- These are **session-only permission overrides** (same syntax as settings.json)
- Higher priority than settings files
- These are **session-only permission rules** (same syntax as settings.json), added to the rules from your settings files
- Deny rules take precedence over allow rules from any source, so `allowedTools` cannot override a matching deny rule
- Works for both built-in tools AND MCP tools
- Cannot use both `allowedTools` and `disallowedTools` together
- Empty `allowedTools: []` = Explicit empty allowlist (no tools allowed)
- `allowedTools` and `disallowedTools` can be used together
- `allowedTools` does not restrict which tools exist; use `tools` for built-in tools
- Empty `allowedTools: []` = no tools pre-approved (not "no tools allowed")
- Omitting the flags entirely = Falls back to normal permission system
- Use `/permissions` in Claude to see all available tool names

Expand All @@ -537,9 +588,12 @@ const result = await generateText({
| Configuration | CLI Flag Behavior | Result |
|--------------|-------------------|---------|
| No config | No `--allowedTools` or `--disallowedTools` | Falls back to settings.json and interactive prompts |
| `allowedTools: []` | `--allowedTools` (empty) | Explicit empty allowlist - blocks all tools |
| `allowedTools: ['Read']` | `--allowedTools Read` | Only allows Read tool |
| `disallowedTools: []` | `--disallowedTools` (empty) | No effect - normal permissions apply |
| `allowedTools: []` | No flag (empty lists are not passed) | No tools pre-approved - normal permissions apply |
| `allowedTools: ['Read']` | `--allowedTools Read` | Read runs without prompting; other tools follow normal permissions |
| `allowedTools: ['Bash', 'Read']` + `disallowedTools: ['Read']` | `--allowedTools Bash,Read --disallowedTools Read` | Read is denied (deny wins); Bash runs without prompting |
| `tools: []` | `--tools` (empty) | No built-in tools available; MCP tools are unaffected |
| `tools: ['Read']` | `--tools Read` | Only the Read built-in tool is available |
| `disallowedTools: []` | No flag (empty lists are not passed) | No effect - normal permissions apply |
| `disallowedTools: ['Write']` | `--disallowedTools Write` | Blocks Write tool, others follow normal permissions |

## Advanced Configuration
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ npx tsx examples/custom-config.ts
npx tsx examples/tool-management.ts
```

**Key concepts**: Allow/deny lists, tool security, MCP server tools, built-in tools
**Key concepts**: `tools` to restrict built-in tools, `allowedTools` pre-approval (not an allowlist), `disallowedTools` deny rules, combining both lists (deny wins), MCP server tools

### 12. Long-Running Tasks (`long-running-tasks.ts`)

Expand Down
Loading
Loading