Course · OpenCode · Extending opencode
Extending OpenCode
feedmill, the course’s illustrative Go feed aggregator, keeps its most important truth outside the repository. In this chapter, assume feedmill keeps the list of feeds it pulls in a Postgres feeds table, not in a config file the agent can read, and its reading queue in a SQLite items table. The errors you want triaged live in a hosted error tracker, not in your scrollback. And the typed Go codebase itself - per-source parsers, each decoding a differently-shaped payload into the same Item struct - knows when you have broken something long before the tests do.
OpenCode can’t reach any of that on its own. Reading a file is one thing; querying a live database, reading a hosted error report, or noticing that the JSON-feed parser you just edited no longer type-checks are three different problems, and OpenCode solves each through a different extension surface. This chapter covers those three surfaces - what each one is for, so you stop reaching for the wrong one.
Widening what the agent can reach re-opens the same trust questions its permission layer answers for ordinary tools (the per-tool, per-agent allow/ask/deny map that governs the tools it already has): who vouches for the new capability, what it may touch, and what stops it when it goes wrong. Each lesson answers those questions for its own surface.
The three are easy to blur, so pin the distinction before we start:
- Reach - an MCP server (Model Context Protocol), declared in the
mcpfield ofopencode.json(OpenCode’s main config file), is a bridge to a system OpenCode otherwise can’t touch (as of October 2026; MCP documentation). Atype: "local"server runs a command (itscommandarray) on your machine; atype: "remote"server points at aurl. Either way it hands the model new tools - query thefeedstable, read an error report - that didn’t exist before. - In-tree intelligence - an LSP server (Language Server Protocol), configured in the separate
lspfield, is the channel OpenCode has into your own code. LSP is off by default; you turn it on with"lsp": truefor the built-in servers, or a per-server block (as of October 2026; LSP documentation). Once it’s enabled the agent receives LSP diagnostics - the same errors your editor underlines in red - as it works, no MCP wrapper required. Without it, the agent sees a compile error only when it runsgo builditself; with it, errors come back after each edit, at the cost of a running language server that can use significant memory and slow the agent, which is why it is off by default. - Gate - a plugin is live JS/TS that subscribes to OpenCode’s lifecycle events, such as
file.editedandtool.execute.before(as of October 2026; plugins documentation). The gate itself is thetool.execute.beforehook: it runs before a tool call and blocks the call by throwing, regardless of what the model decided in the moment. It holds only for the tools its code matches, so a guard written for theedittool does not stop a write throughbash. Reach for it when a permission rule can’t see enough: a rule decides on the tool name and one pattern, and an MCP tool, which sits in the same map under its<server>_<tool>name, is matched by name alone. The hook’s code reads the call’s arguments, so it can refuse one call and allow the next (as of October 2026;session/tools.ts).
What you’ll do this chapter
Section titled “What you’ll do this chapter”- Reach external systems over MCP - declare the Postgres database and the hosted error tracker as
mcpservers, local and remote, and let OpenCode query the source of truth instead of guessing from the repo. - Turn on the LSP channel - wire up the
lspfield so OpenCode reads Go diagnostics directly while it edits a parser, catching the type error before a test run ever does. - Gate moves with plugin hooks - write a plugin that lints after every
file.editedevent and throws intool.execute.beforeto block the move you never want to happen, so the guardrail fires whether or not the model remembered it - and register a custom tool from the same module.
The lessons build in that order because it’s the order the need shows up in real work: first you widen what the agent can reach beyond the repo, then you tighten the feedback it gets inside the repo, then you put a wall around the moves that must never go wrong.
A custom tool also gives the agent reach, so it can look like an alternative to MCP. The difference is where the code runs: a custom tool is JS/TS that runs inside OpenCode and serves only OpenCode (as of October 2026; custom tools documentation), while an MCP server is a separate process or URL that any MCP client can use. Write a custom tool for a verb only this project needs; declare an MCP server when one already exists for the system or other clients need the same reach.
To follow along in your own repo, you need a database you can reach (any database with an MCP server can stand in for Postgres) and a typed language with a language server. The error tracker is an example only. By the end you’ll know not just how to declare an MCP server or author a plugin, but which surface a given problem calls for.
The first surface to wire is the widest: reach the feeds table over MCP.