Skip to content
Open
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
23 changes: 23 additions & 0 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export namespace Agent {
edit: Config.Permission,
bash: z.record(z.string(), Config.Permission),
webfetch: Config.Permission.optional(),
mcp: z.record(z.string(), Config.Permission).optional(),
}),
model: z
.object({
Expand All @@ -45,6 +46,9 @@ export namespace Agent {
"*": "allow",
},
webfetch: "allow",
mcp: {
"*": "allow",
},
}
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})

Expand All @@ -53,6 +57,7 @@ export namespace Agent {
edit: "deny",
bash: "ask",
webfetch: "allow",
mcp: "ask",
},
cfg.permission ?? {},
)
Expand Down Expand Up @@ -195,10 +200,28 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag
}
}

let mergedMcp
if (merged.mcp) {
if (typeof merged.mcp === "string") {
mergedMcp = {
"*": merged.mcp,
}
}
if (typeof merged.mcp === "object") {
mergedMcp = mergeDeep(
{
"*": "ask",
},
merged.mcp,
)
}
}

const result: Agent.Info["permission"] = {
edit: merged.edit ?? "allow",
webfetch: merged.webfetch ?? "allow",
bash: mergedBash ?? { "*": "allow" },
mcp: mergedMcp ?? { "*": "allow" },
}

return result
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export namespace Config {
edit: Permission.optional(),
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
webfetch: Permission.optional(),
mcp: z.union([Permission, z.record(z.string(), Permission)]).optional(),
})
.optional(),
})
Expand Down Expand Up @@ -554,6 +555,7 @@ export namespace Config {
edit: Permission.optional(),
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
webfetch: Permission.optional(),
mcp: z.union([Permission, z.record(z.string(), Permission)]).optional(),
})
.optional(),
tools: z.record(z.string(), z.boolean()).optional(),
Expand Down
24 changes: 24 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,30 @@ export namespace SessionPrompt {
const execute = item.execute
if (!execute) continue
item.execute = async (args, opts) => {
const permissions = input.agent.permission?.mcp
const action = Wildcard.all(key, permissions ?? {})

if (action === "deny") {
throw new Error(
`The user has specifically restricted access to this MCP tool, you are not allowed to execute it. Tool: ${key}`,
)
}

if (action === "ask") {
await Permission.ask({
type: "mcp",
pattern: key,
sessionID: input.sessionID,
messageID: input.processor.message.id,
callID: opts.toolCallId,
title: `MCP tool: ${key}`,
metadata: {
tool: key,
args,
},
})
}

await Plugin.trigger(
"tool.execute.before",
{
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/js/src/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ export type AgentConfig = {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
mcp?:
| ("ask" | "allow" | "deny")
| {
[key: string]: "ask" | "allow" | "deny"
}
}
[key: string]:
| unknown
Expand All @@ -252,6 +257,11 @@ export type AgentConfig = {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
mcp?:
| ("ask" | "allow" | "deny")
| {
[key: string]: "ask" | "allow" | "deny"
}
}
| undefined
}
Expand Down Expand Up @@ -484,6 +494,11 @@ export type Config = {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
mcp?:
| ("ask" | "allow" | "deny")
| {
[key: string]: "ask" | "allow" | "deny"
}
}
tools?: {
[key: string]: boolean
Expand Down Expand Up @@ -1000,6 +1015,9 @@ export type Agent = {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
mcp?: {
[key: string]: "ask" | "allow" | "deny"
}
}
model?: {
modelID: string
Expand Down
28 changes: 27 additions & 1 deletion packages/web/src/content/docs/permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This lets you configure granular controls for the `edit`, `bash`, and `webfetch`

## Tools

Currently, the permissions for the `edit`, `bash`, and `webfetch` tools can be configured through the `permission` option.
Currently, the permissions for the `edit`, `bash`, `webfetch`, and `mcp` tools can be configured through the `permission` option.

---

Expand Down Expand Up @@ -145,6 +145,32 @@ Use the `permission.webfetch` key to control whether the LLM can fetch web pages

---

### mcp

Use the `permission.mcp` key to control whether MCP tools require user approval.

```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"mcp": {
"*": "ask",
"mymcp1_tool_name1": "allow",
"mymcp2_*": "deny"
}
}
}
```

You can use wildcards to manage permissions for specific MCP tools:

- `"*"` matches all MCP tools
- `"mymcp1_tool_name1"` matches tool `tool_name1` of mcp server `mymcp1`
- `"mymcp2_*"` matches all tools starting with `mymcp2_`
- Specific rules override wildcard rules

---

## Agents

You can also configure permissions per agent. Where the agent specific config
Expand Down