Yes. OpenCode is designed to be extended with custom tools—small functions that the model can call during a conversation, alongside built-in tools like read, write, and bash. You create a tool as a JavaScript or TypeScript module that exports a tool definition, and OpenCode automatically discovers it when it’s placed in the right directory. The key point is that the “tool definition” is JS/TS, but the implementation can invoke scripts in any language (Python, Go, Bash, etc.); JS/TS is just the wrapper that declares the interface, validates arguments, and calls your code.
In practice, you put project-specific tools in .opencode/tools/ inside your repo, or you put global tools in ~/.config/opencode/tools/ so they’re available everywhere. The filename becomes the tool name, and you define the tool using the tool() helper from @opencode-ai/plugin, which gives you type checking and argument validation. A minimal tool file looks like: import { tool } from "@opencode-ai/plugin"; export default tool({ description: "...", args: { ...schema... }, async execute(args, ctx) { ... } }). OpenCode supports multiple tools per file too: if you export multiple named tools from a file, each exported tool is registered with a compound name. This makes it easy to bundle a small toolbox for a repo—for example, lint_fix, test_run, and changelog_update—without creating dozens of files. Once the tool exists, you can ask OpenCode to call it naturally (“run the database migration tool”), or you can design prompts so the agent chooses it when needed.
Custom tools are also where OpenCode becomes genuinely “workflow-shaped” instead of generic. For example, if you’re building a semantic search service using a vector database such as Milvus or Zilliz Cloud, a custom tool can wrap common debugging and operations tasks: “check collection schema,” “run a nearest-neighbor query for this embedding,” “compare recall before/after changing index params,” or “rebuild an index in a staging environment.” Concretely, your tool’s execute() can shell out to a Python script that uses pymilvus, or it can call an internal HTTP endpoint that your service exposes for diagnostics. The safety upside is that you can keep those actions narrow and audited (one tool does one thing), and then use OpenCode’s permissions to require approval before the tool runs. That combination—custom tools + permissions—lets you extend OpenCode while still keeping control over side effects.
