Why We Reviewed OpenCode for Team Onboarding
The expensive part of adopting another coding agent is not getting its first response. It is explaining why one engineer gets the company gateway, another gets a personal provider account, and a third cannot see the MCP tools everyone else has.
Configuration is not proof of portability
Provider credentials and MCP authentication are separate, and no pinned OpenCode runtime was tested; verify routing and actual tool execution before deployment.
That is the onboarding problem we targeted with OpenCode: a terminal-first coding agent with configurable model providers and local or remote MCP connections. Under the hood, these are separate integrations. A model provider handles inference; an MCP server exposes tools and other capabilities. Connecting one does not authenticate the other.
Our evidence boundary matters: we reviewed the supplied OpenCode V2 configuration, MCP, and provider references, but did not execute a pinned OpenCode binary in a sample repository. The walkthrough below is a reproducible worked example, not a successful deployment transcript. Its example output is explicitly simulated. We have no measured provider-swap results, session-restoration results, or load benchmark.
That limits our verdict, but it does not eliminate useful architectural conclusions. Our review identified several deployment-sensitive boundaries:
- Project configuration and machine-wide configuration have different precedence.
- Provider credentials belong to the process making requests, not necessarily the terminal launching the interface.
- Remote MCP connections default to OAuth.
- A registered MCP server is not proof of a successful tool invocation.
- An endpoint change does not automatically change the provider runtime or request format.
We would evaluate OpenCode for teams that want explicit provider routing and terminal workflows. We would not approve it merely because a model appears in a picker.
Our tools collection covers the broader agent landscape. Here, we focus on the configuration contract that must survive onboarding and account changes.
Hands-On Walkthrough: Setup, Execution & Output
We built this walkthrough around the V2 shapes in the OpenCode configuration reference. We would start with an installed CLI from the official OpenCode entry point, then record the actual binary version before changing anything.
The supplied material does not establish a release number or a version-pinned installation command. We therefore would not put an unversioned installer into a team bootstrap script and call the result reproducible.
With Git, Node.js/npm, and OpenCode installed, this is the registration sequence we would run:
set -eu
mkdir opencode-onboarding-review
cd opencode-onboarding-review
git init -q
opencode --version
cat > opencode.jsonc <<'JSONC'
{
"$schema": "https://opencode.ai/config.json"
}
JSONC
opencode mcp add everything \
--env LOG_LEVEL=info \
-- npx -y @modelcontextprotocol/server-everything
printf '\n--- Review generated configuration ---\n'
git diff --no-index -- /dev/null opencode.jsonc || true
printf '\n--- Check MCP connection state ---\n'
opencode mcp list
Simulated output, not a captured run:
<installed OpenCode version>
--- Review generated configuration ---
<configuration diff; verify mcp.servers.everything>
--- Check MCP connection state ---
<connection status; verify successful initialization>
We deliberately avoid inventing exact success messages or exit codes. We would also inspect the repository for other generated configuration files. We expect project-scoped registration to write to project configuration, but would verify the actual filename with the installed CLI rather than assume it always writes JSONC.
We took the local registration command from the MCP setup reference. Everything after -- is the server command and its arguments. OpenCode starts that process over stdio. Node/npm must therefore be available to the process launching the server.
The example uses npx -y for convenience. For a team rollout, we would pin the MCP package version after validation rather than download an unconstrained package during onboarding.
The configuration we would commit
For the next stage, we would use an explicit provider definition alongside the local MCP server:
{
"$schema": "https://opencode.ai/config.json",
"model": "lab/qwen3-coder",
"providers": {
"lab": {
"name": "Lab Compatible Endpoint",
"env": ["LAB_API_KEY"],
"package": "@opencode/ai/providers/openai-compatible",
"settings": {
"baseURL": "http://127.0.0.1:8000/v1"
},
"models": {
"qwen3-coder": {
"name": "Lab Coding Model"
}
}
}
},
"mcp": {
"servers": {
"everything": {
"type": "local",
"command": [
"npx",
"-y",
"@modelcontextprotocol/server-everything"
],
"cwd": ".",
"environment": {
"LOG_LEVEL": "info"
}
}
}
}
}
This is a configuration example, not an endpoint implementation. It requires a separately running OpenAI-compatible service that exposes the configured model. The loopback address is relative to the OpenCode server process; inside a container, it does not magically reach the host.
We would start a private server with its credential available at launch:
export LAB_API_KEY="replace-with-your-endpoint-credential"
opencode --standalone
For a catalog provider, our interactive sequence is:
/connect
/models
We use /connect to establish the provider connection, then /models to select a model. We do not treat connecting an account as selecting it for every session.
We keep the provider ID, OpenCode model ID, and optional upstream modelID separate in our provider configuration. In this example, lab/qwen3-coder is the OpenCode reference. If the endpoint expects a different model name, we would add modelID rather than assume the display name controls routing.
Our provider-swap acceptance test
We would run the same small repository task against two providers: inspect a function, make one bounded change, run its test, and invoke one MCP tool. For each request, we would verify the destination, upstream model ID, and credential identity using redacted server-side telemetry.
We would repeat that sequence in a fresh session and an existing session. The missing evidence is important: we cannot claim that changing providers preserves model selection, conversation state, compaction state, or MCP context correctly. Those remain acceptance tests.
Deployment Hazards Identified in Our Configuration Review
Our immediate roadblock was verification depth: without a pinned runtime, we could not turn configuration review into observed failure transcripts. The following are concrete deployment hazards we identified, not failures we claim to have reproduced.
V1-shaped examples can invalidate a V2 rollout
We anchor our configuration to V2:
{
"mcp": {
"servers": {
"example": {
"type": "local",
"command": ["example-mcp-server"],
"disabled": true
}
}
}
}
We use mcp.servers, not server names directly beneath mcp. We use disabled, not an enabled flag.
Our workaround is straightforward: keep the schema reference, record the CLI version, and review migration-sensitive examples before copying them. Editor validation helps, but a moving schema URL is not a substitute for validating the installed release.
A same-name MCP override replaces the server object
We would not put a command in global configuration and override only its environment in project configuration.
A higher-precedence project definition replaces the entire same-name MCP server object. Our project definition must repeat every required field, or use a distinct server name.
Configuration discovery adds another trap. Direct opencode.json(c) files are merged first, followed by discovered .opencode configurations. Consequently, an ancestor .opencode configuration can override a closer direct configuration. System-managed settings outrank project settings.
Our onboarding checklist therefore includes ancestor directories and managed settings, not just the repository root.
Exporting a key in the terminal may change nothing
We treat this as the most consequential provider-swap hazard.
We configure environment credentials on the OpenCode server process that makes model requests, not merely in the terminal launching the interface. For a shared background server, changing the launching terminal's environment is not sufficient evidence that the running service received the new value.
We would use standalone mode for isolation or deliberately update the managed service environment:
opencode service set env ANTHROPIC_API_KEY "$ANTHROPIC_API_KEY"
opencode auth list
That service operation stops a running service; the next OpenCode command starts it with the updated value.
Saved accounts also take precedence over environment connections for the same integration. We would inspect active accounts before concluding that a new environment key is in use. Conversely, auth logout cannot remove an environment connection; we must unset its variable.
We also avoid building CI around piped API-key entry. API-key forms require an interactive terminal.
Environment substitution can fail quietly
Inside configuration, we use {env:NAME}, not $NAME. An unset variable becomes an empty string.
For a header-only remote MCP service, our pattern is:
{
"mcp": {
"servers": {
"company-tools": {
"type": "remote",
"url": "https://mcp.example.com/mcp",
"oauth": false,
"headers": {
"Authorization": "Bearer {env:MCP_API_KEY}"
}
}
}
}
}
The URL is a placeholder, not a runnable public service. Before launch, we fail explicitly:
: "${MCP_API_KEY:?Set MCP_API_KEY before launching OpenCode}"
We disable OAuth only when the service exclusively uses header credentials. We would also redact headers and callback URLs ourselves; we have not verified automatic token redaction in logs or status output.
Remote authentication is a separate workflow
Our remote registration sequence is:
opencode mcp add context7 --url https://mcp.context7.com/mcp
opencode mcp list
We add --global only when every project should inherit that server. If authentication is required, we use /mcps or opencode mcp auth <name>.
Over SSH or inside a container, the browser's loopback callback may not reach OpenCode. The supported recovery is to paste the full callback URL into the waiting sign-in prompt. We treat that URL as sensitive authorization material.
For enterprise OAuth, we check client registration support. A pre-registered client_id takes priority; otherwise CIMD can apply, followed by dynamic registration. If neither discovery-based route is supported, we configure a pre-registered client.
We could not verify the requested Composio-specific setup path because its guide was absent from the supplied material. We would require the exact endpoint, transport, authentication contract, and a successful tool call before approving that integration.
Scale, Latency & Cost vs. Alternatives
We have no throughput, memory, or coding-quality measurements for OpenCode, Cline, Cursor, or Codex CLI in this review. Our comparison is an architectural selection guide, not a benchmark ranking.
| Option | When we would shortlist it | What we would validate before rollout |
|---|---|---|
| OpenCode | Terminal workflow with explicit provider and MCP configuration | Server environment, account precedence, session swaps, tool execution |
| Cline | Editor-centered agent evaluation | Current provider support, MCP lifecycle, approval controls |
| Cursor | Integrated editor workflow | Plan terms, model routing, enterprise controls, required integrations |
| Codex CLI | Terminal workflow in an OpenAI-oriented evaluation | Authentication, execution controls, MCP behavior, portability needs |
We would not infer lower operating cost from provider choice alone. A cheap model that repeatedly fails tool calls can cost more in engineer time than an expensive model that completes the task.
Our scaling concern is connection multiplication. By default, each open directory connects to its MCP servers independently. Twenty directories and four servers can imply up to eighty connections or local processes before accounting for failures and reconnections. That is arithmetic from the connection model, not an observed load result.
We would not blindly enable pooling. Shared connections require the 2026-07-28 protocol revision and matching connection settings. Older servers remain per-directory. Local sharing also needs a fixed working directory; pooled connections have no roots, which deserves a compatibility test.
Timeouts require explicit operational decisions:
| Boundary | Reference default | Our deployment concern |
|---|---|---|
| MCP startup | 30 seconds | Slow initialization delays readiness |
| MCP catalog | 30 seconds | Large or stalled discovery affects availability |
| MCP execution | 12 hours | Too permissive for many interactive workflows |
| Provider response headers | 5 minutes | Failure detection may exceed team expectations |
| Provider streamed-chunk gap | 15 minutes over HTTP | A stalled stream can remain open a long time |
These are configured defaults, not measured latency. Provider timeout failures can be retried up to three times, so we would not equate one timeout value with total elapsed failure time.
We would also keep protocol: "auto" scoped narrowly. Probing a legacy local server can create an extra short-lived process and wait up to the startup timeout if unknown requests are ignored.
For cost, our break-even model is:
Monthly benefit = avoided seat spend + inference savings − maintenance labor − added infrastructure.
As an illustrative scenario, twenty avoided seats at $20 each yield $400 monthly. Adding $150 in inference savings produces $550 before maintenance. At a loaded engineering rate of $100/hour, 5.5 maintenance hours consume that benefit. These are assumptions, not vendor prices.
Our decision is therefore less about installing a free CLI and more about keeping its integration overhead below the savings.
What We Could Not Verify
We did not run a pinned OpenCode binary. We could not verify provider swaps, session restoration, load behavior, automatic token redaction, or the Composio-specific setup path.
Our Final Verdict: When to Deploy, When to Skip
Our verdict is shortlist OpenCode for a controlled pilot, but do not mistake configurable routing for proven portability.
We would deploy a pilot if:
- We want a terminal-first workflow.
- We can pin and record CLI and MCP package versions.
- We have explicit ownership of provider accounts and server environment.
- We can verify routing with redacted telemetry.
- We can test actual MCP execution, not just registration.
- We can validate fresh-session and existing-session provider swaps separately.
We would hold off if:
- We need a proven unattended onboarding path immediately.
- Our remote integration depends on unverified Composio behavior.
- We expect configuration edits to migrate session state automatically.
- We need measured load characteristics before procurement.
- We cannot inspect managed configuration or credential precedence.
Before standardization, our minimum evidence would be a pinned version, reviewed generated configuration, confirmed provider identity, one successful MCP invocation, an account-switch check, and a session-swap test.
OpenCode gives us useful configuration surfaces. The unresolved question is whether those surfaces behave predictably in the team's actual runtime and repository layout. We would answer that with a small acceptance suite before expanding deployment.
For a broader shortlist, browse our tools collection. For help designing provider-portability and MCP acceptance checks around your own repositories, talk to our engineering team.
Top comments (1)
This is the rare review that separates "configured" from "working" - the registered-is-not-invoked distinction is exactly where most onboarding guides stop.
The precedence point deserves the same empirical treatment as the others. Don't just document that project config beats machine config - prove it once per environment. Set a deliberately wrong value at machine scope and the right one at project scope, then check which the runtime actually reports. Two minutes, and a doc claim becomes a verified gate. Same for provider routing: one real inference call through each configured provider, recorded, before anyone calls onboarding done.
And +1 on pinning after validation. The failure mode worth a line in the runbook is the mirror image: a pinned MCP package whose server contract drifts underneath you (a tool renamed, a signature changed). Pinning freezes your side; a single smoke invocation at startup catches theirs. Together they are what makes "it worked during onboarding" survive into month two.