OpenCode Custom Tools
Custom Tools allow you to extend OpenCode's capabilities, enabling the LLM to call your defined functions during conversations to implement complex operations such as database queries, script execution, API calls, and more.
These tools work alongside built-in tools (such as read, write, bash) to build a complete automated development capability system.
Custom tools are OpenCode's core extension capability, upgrading the LLM from merely generating code to actually executing tasks.
Through well-designed tools, you can build:
- Automated development pipelines
- Intelligent code assistants
- AI operations systems
- Enterprise internal AI tool platforms
1. What Are Custom Tools
Custom tools are essentially function interfaces that the LLM can actively call during runtime and obtain execution results.
Typical application scenarios:
- Query databases (SQL / NoSQL)
- Call internal APIs
- Execute scripts (Python / Shell)
- Read system status or environment information
- Encapsulate complex business logic
You can understand it as: a plugin mechanism that gives AI execution capabilities.
2. Tool Storage Locations
OpenCode automatically loads tools from the following directories:
- Project-level:
.opencode/tools/ - Global-level:
~/.config/opencode/tools/
The filename becomes the tool name (very important).
3. Basic Tool Definition (Recommended Method)
Using the tool() helper function provides type safety and parameter validation capabilities.
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Query the project database",
args: {
query: tool.schema.string().describe("SQL query to execute"),
},
async execute(args) {
return `Executed query: ${args.query}`
},
})
Explanation:
description: Tool description, used by the LLM to determine whether to call itargs: Parameter definition (based on Zod)execute: Actual execution logic
4. Multiple Tools in a Single File
A single file can export multiple tools, each automatically named:
<filename>_<export_name>
import { tool } from "@opencode-ai/plugin"
export const add = tool({
description: "Add two numbers",
args: {
a: tool.schema.number(),
b: tool.schema.number(),
},
async execute(args) {
return args.a + args.b
},
})
export const multiply = tool({
description: "Multiply two numbers",
args: {
a: tool.schema.number(),
b: tool.schema.number(),
},
async execute(args) {
return args.a * args.b
},
})
Final generated tools:
math_addmath_multiply
5. Conflicts with Built-in Tools
If your tool name is the same as a built-in tool, it will override the built-in tool.
// Override built-in bash tool
export default tool({
description: "Restricted bash wrapper",
args: {
command: tool.schema.string(),
},
async execute(args) {
return `blocked: ${args.command}`
},
})
Recommendations:
- Avoid naming conflicts
- If you only need to restrict capabilities, prefer using
permissionconfiguration
6. Parameter Definition (Zod)
Parameters are defined using Zod Schema, supporting type validation and descriptions:
args: {
query: tool.schema.string().describe("SQL query"),
limit: tool.schema.number().optional(),
}
You can also use Zod directly:
import { z } from "zod"
export default {
description: "Example tool",
args: {
name: z.string(),
},
async execute(args) {
return `Hello ${args.name}`
},
}
7. Context
Context information is automatically injected when tools execute:
export default tool({
description: "Get project info",
args: {},
async execute(args, context) {
const { agent, sessionID, directory, worktree } = context
return `Agent: ${agent}, Dir: ${directory}`
},
})<
Available fields:
agent: Current agentsessionID: Session IDmessageID: Message IDdirectory: Current working directoryworktree: Git root directory
8. Calling External Languages (Python Example)
You can implement tool logic in any language, such as Python.
1. Python Script
# .opencode/tools/add.py
import sys
a = int(sys.argv)
b = int(sys.argv)
print(a + b)
2. Tool Wrapper
import { tool } from "@opencode-ai/plugin"
import path from "path"
export default tool({
description: "Add two numbers using Python",
args: {
a: tool.schema.number(),
b: tool.schema.number(),
},
async execute(args, context) {
const script = path.join(context.worktree, ".opencode/tools/add.py")
const result = await Bun.$`python3 ${script} ${args.a} ${args.b}`.text()
return result.trim()
},
})
This approach is suitable for:
- Existing Python toolchains
- Calling data analysis / AI models
- Executing complex computational tasks
9. Best Practices
- Tool descriptions should be clear to help the LLM choose correctly
- Parameter design should be simple and explicit
- Avoid side effects (unless explicitly needed)
- Prefer controlling risky operations through permission configuration
- Tools should focus on single responsibilities
YouTip