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(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of "mcp" i think permissions need to handle wildcards exactly like tools, so instead of introducing an mcp object it would just be handle the "" or "context7" wildcards and enforce the permissions accordingly much like tools

}),
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(),
Copy link
Collaborator

@rekram1-node rekram1-node Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry if i wasnt being clear :)

What i was saying was that instead of adding "mcp" here, you could add a catchall(...) in here:
permission: z
.object({
edit: Permission.optional(),
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
webfetch: Permission.optional(),
})
.optional(),

That way permissions function similar to tools:
tools: z.record(z.string(), z.boolean()).optional(),

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because mcp things are same as any other tool, there are also custom tools, etc

})
.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