Hook
When building local-first architectures, the instinct is often to tightly couple everything. If a system needs a new shell execution tool or a data filter, the default move is to drop it in as an in-memory plugin. It feels like the cleanest path: it runs in the same process, shares the same lifecycle, and requires zero initial configuration.
But that tight coupling is exactly where local systems fracture.
As the environment grows, the plugin model starts exposing its limits. You lose process ancestry when a local daemon restarts. You hit cold caches on reconnections. You lose your audit trail because plugins can't log independently from the host process, burying critical observability.
What starts as a simple 20-line feature turns into an architectural bottleneck.
The problem isn't the code itself — it is the execution boundaries. The difference between a fragile monolithic local setup and a resilient, modular ecosystem comes down to the architectural decision made before the first line of code is written: Should this be a Plugin, a standalone MCP Server, or a CLI Bridge?
The Wrong Assumption
The pattern we followed at first was: new capability equals new plugin. It was the obvious path — OpenCode's plugin system is well-documented, auto-discovered from ~/.config/opencode/plugins/, zero deployment. Drop a .ts file, restart, it works.
The first plugin was fine. The second was fine. By the sixth, we had the same two bugs across three different frameworks:
- Process ancestry loss. Every plugin inherits OpenCode's process table. When the daemon restarts (config change, crash, update), every plugin restarts with it — cold cache, empty connection pools, every in-flight task evaporated.
- Silent death. A plugin crash doesn't surface. The parent process catches the exception, logs it to its own log, and continues. The plugin is gone. No one knows until the next session fails to find the tool.
The first time I noticed was when ark-delegator stopped routing tasks. No error. No alert. Just a silent gap in the process table. The plugin had died with the daemon restart three hours earlier.
The Decision Tree
Four layers. Each layer is one decision, not a configuration. The wrong choice at any layer compounds through every later choice.
Layer 1: Plugin or MCP Server?
Plugin?
→ Same process: zero config, zero network, zero deployment
→ Same mortality: daemon restart = plugin restart
→ Same visibility: plugin crash = silent death
→ No independent logging: must use parent's logger
MCP Server?
→ Own process: survives client death, restarts independently
→ Own logs: stdout/stderr captured by supervisor
→ Network boundary: SSE port, reachable from any client
→ +58ms overhead: stdio establishment per request
Answer: If the tool must survive a daemon restart — and every production tool should — it's an MCP server. The plugin is for prototyping, not production.
We discovered this ordering wrong. We started with plugins because they were easier. We should have started with servers because they survive.
Layer 2: Stdio or SSE?
Stdio (child process)?
→ Zero network: works offline, no latency
→ No auth: stdin/stdout within same machine
→ Version-controlled: pin exact npm version
→ Process isolation: crash doesn't cascade
→ 2.7GB RAM per session — 45 processes for 6 servers
SSE (remote endpoint)?
→ Shareable: one server, N clients
→ Survives sessions: client disconnects, server stays
→ Port-based: numbered allocation, no collision
→ Docker-compatible: map ports, exec from any container
→ ~3 seconds startup per server (supergateway)
Answer: Stdio for single-machine, single-session dev. SSE for anything shared between clients, sessions, or machines.
Our first approach was npx-based stdio for everything: npx @ev3lynx/oh-my-mcp spawned three processes (sh → npx → node), each server consumed ~450MB, and every new WSL instance needed the same install. We ran out of RAM at 6 servers. The switch to SSE via supergateway cut per-server overhead in half and made them shareable.
Layer 3: Focused or Monolith?
Focused (3-8 tools)?
→ 8,000-10,000 tokens total schema for all 6 servers
→ Agent loads only what it needs for current operation
→ Independent versioning: update one without touching others
→ Easy to replace: swap one server, not the catalog
Monolith (93 tools)?
→ 55,000 tokens of schema — $51,000/month at scale
→ Everything in every prompt, used or not
→ One bad tool update breaks the whole server
→ Hard to audit: 93 tools, who uses which?
Answer: If a tool group fits in 3-8 operations, it's a focused server. If it exceeds 8, split. The constraint isn't technical — it's cognitive. 8 tools is the number of items the human brain can hold in working memory.
We made this mistake once: a single GitHub server with 93 tools. The schema tax was 55K tokens. We never used 80 of those tools. The fix was replacing it with 3 focused servers (repo ops, issue tracking, code search).
Layer 4: MCP Tool or CLI Bridge?
MCP Tool?
→ Structured output: known schema, capped response
→ 170 tokens per call, schema overhead per session
→ Best for: query, search, read operations
CLI Bridge?
→ Unstructured: whatever the command returns (RTK-filtered)
→ 50 tokens per call, zero schema overhead
→ Best for: execute, push, write operations
→ Audit trail: JSONL logger, exit codes, duration
Answer: Read through MCP, write through CLI. Queries need structure. Actions need speed.
We spent a week building an MCP tool for git push. It returned "done" in JSON. We deleted it and went back to git push origin main — 50 tokens, same result, fewer lines of code to maintain.
The Rule That Handles 95% of Decisions
After four layers of mistakes, one rule covers almost everything:
Is the operation a query? → MCP tool (structured, typed)
Is it a side effect with small output? → CLI bridge (fast, audited)
Does it need to survive a restart? → MCP server (own process)
Is it a prototype? → Plugin (fast, temporary)
Does it run on more than one machine? → Container (one image, many clients)
This isn't theory. These are the rules we extracted from production failures. Every wrong choice is documented in a closed PR, a deleted file, or a systemd restart that wiped a session.
The Closing
The PR that took 77 days wasn't slow because the code was hard. It was slow because we were solving the wrong problem — fitting a server-shaped capability into a plugin-shaped hole. The moment we admitted the architecture was wrong, the implementation took three days.
The stack: oh-my-mcp + commands-rtk. The decision tree: four layers, one rule. The lesson: the architectural decision before the first line of code determines whether delivery takes 77 days or three.
An MCP server isn't a plugin you run differently. It's a process that survives its client.
Top comments (1)
A good decision tree should include ownership and blast radius, not only transport. A CLI bridge is often ideal for deterministic local work, while an MCP server earns its operational cost when several clients need stable schemas, authorization, observability, and a shared tool contract.