diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc
index 28ac3c4f89..0833130012 100644
--- a/.opencode/opencode.jsonc
+++ b/.opencode/opencode.jsonc
@@ -11,7 +11,12 @@
"options": {},
},
},
- "mcp": {},
+ "mcp": {
+ "context7": {
+ "type": "remote",
+ "url": "https://mcp.context7.com/mcp",
+ },
+ },
"tools": {
"github-triage": false,
},
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
index 9e944c330f..a9a2246278 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
@@ -75,14 +75,16 @@ function EditBody(props: { request: PermissionRequest }) {
)
}
-function TextBody(props: { title: string; description?: string; icon: string }) {
+function TextBody(props: { title: string; description?: string; icon?: string }) {
const { theme } = useTheme()
return (
<>
-
- {props.icon}
-
+
+
+ {props.icon}
+
+
{props.title}
@@ -113,12 +115,33 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
return {}
})
+ const { theme } = useTheme()
+
return (
}
+ body={
+
+
+
+
+
+
+ Applies to the following patterns
+
+ {(pattern) => (
+
+ {"- "}
+ {pattern}
+
+ )}
+
+
+
+
+ }
options={{ confirm: "Confirm", cancel: "Cancel" }}
onSelect={(option) => {
if (option === "cancel") {
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index aa681bdbe1..b51c567fb7 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -9,7 +9,7 @@ import { SessionRevert } from "./revert"
import { Session } from "."
import { Agent } from "../agent/agent"
import { Provider } from "../provider/provider"
-import { type Tool as AITool, tool, jsonSchema } from "ai"
+import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions } from "ai"
import { SessionCompaction } from "./compaction"
import { Instance } from "../project/instance"
import { Bus } from "../bus"
@@ -596,6 +596,41 @@ export namespace SessionPrompt {
}) {
using _ = log.time("resolveTools")
const tools: Record = {}
+
+ const context = (args: any, options: ToolCallOptions): Tool.Context => ({
+ sessionID: input.session.id,
+ abort: options.abortSignal!,
+ messageID: input.processor.message.id,
+ callID: options.toolCallId,
+ extra: { model: input.model },
+ agent: input.agent.name,
+ metadata: async (val: { title?: string; metadata?: any }) => {
+ const match = input.processor.partFromToolCall(options.toolCallId)
+ if (match && match.state.status === "running") {
+ await Session.updatePart({
+ ...match,
+ state: {
+ title: val.title,
+ metadata: val.metadata,
+ status: "running",
+ input: args,
+ time: {
+ start: Date.now(),
+ },
+ },
+ })
+ }
+ },
+ async ask(req) {
+ await PermissionNext.ask({
+ ...req,
+ sessionID: input.session.parentID ?? input.session.id,
+ tool: { messageID: input.processor.message.id, callID: options.toolCallId },
+ ruleset: input.agent.permission,
+ })
+ },
+ })
+
for (const item of await ToolRegistry.tools(input.model.providerID)) {
const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
tools[item.id] = tool({
@@ -603,57 +638,25 @@ export namespace SessionPrompt {
description: item.description,
inputSchema: jsonSchema(schema as any),
async execute(args, options) {
+ const ctx = context(args, options)
await Plugin.trigger(
"tool.execute.before",
{
tool: item.id,
- sessionID: input.session.id,
- callID: options.toolCallId,
+ sessionID: ctx.sessionID,
+ callID: ctx.callID,
},
{
args,
},
)
- const ctx: Tool.Context = {
- sessionID: input.session.id,
- abort: options.abortSignal!,
- messageID: input.processor.message.id,
- callID: options.toolCallId,
- extra: { model: input.model },
- agent: input.agent.name,
- metadata: async (val: { title?: string; metadata?: any }) => {
- const match = input.processor.partFromToolCall(options.toolCallId)
- if (match && match.state.status === "running") {
- await Session.updatePart({
- ...match,
- state: {
- title: val.title,
- metadata: val.metadata,
- status: "running",
- input: args,
- time: {
- start: Date.now(),
- },
- },
- })
- }
- },
- async ask(req) {
- await PermissionNext.ask({
- ...req,
- sessionID: input.session.parentID ?? input.session.id,
- tool: { messageID: input.processor.message.id, callID: options.toolCallId },
- ruleset: input.agent.permission,
- })
- },
- }
const result = await item.execute(args, ctx)
await Plugin.trigger(
"tool.execute.after",
{
tool: item.id,
- sessionID: input.session.id,
- callID: options.toolCallId,
+ sessionID: ctx.sessionID,
+ callID: ctx.callID,
},
result,
)
@@ -667,30 +670,41 @@ export namespace SessionPrompt {
},
})
}
+
for (const [key, item] of Object.entries(await MCP.tools())) {
const execute = item.execute
if (!execute) continue
// Wrap execute to add plugin hooks and format output
item.execute = async (args, opts) => {
+ const ctx = context(args, opts)
+
await Plugin.trigger(
"tool.execute.before",
{
tool: key,
- sessionID: input.session.id,
+ sessionID: ctx.sessionID,
callID: opts.toolCallId,
},
{
args,
},
)
+
+ await ctx.ask({
+ permission: key,
+ metadata: {},
+ patterns: ["*"],
+ always: ["*"],
+ })
+
const result = await execute(args, opts)
await Plugin.trigger(
"tool.execute.after",
{
tool: key,
- sessionID: input.session.id,
+ sessionID: ctx.sessionID,
callID: opts.toolCallId,
},
result,
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index 9a984db0aa..db51528474 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -2,7 +2,6 @@ import { BashTool } from "./bash"
import { EditTool } from "./edit"
import { GlobTool } from "./glob"
import { GrepTool } from "./grep"
-import { ListTool } from "./ls"
import { BatchTool } from "./batch"
import { ReadTool } from "./read"
import { TaskTool } from "./task"