# OpenCode MCP Setup: opencode.json for v1 and v2

> OpenCode MCP setup checked on November 6, 2026: local and remote entries in opencode.json for v1 and v2, API key headers vs OAuth, and prompts that install UI.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Nov 6, 2026
- Category: [Building UI with AI agents](https://wingo-ui.com/blog/category/ai-agents)
- Reading time: 11 min
- Canonical: https://wingo-ui.com/blog/opencode-mcp

## TL;DR

OpenCode reads MCP servers from the mcp key of opencode.json: in OpenCode 1 each server sits directly under mcp with enabled, in OpenCode 2 it sits under mcp.servers with disabled, and OpenCode 2 still reads the old shape. A local entry runs a command array such as npx -y wingo-ui@latest mcp; a remote entry takes a url plus headers, and with an API key you set oauth to false. Run opencode mcp list to check the connection, then ask for components by name.

Ask OpenCode for a confirm dialog and it writes one from memory: a fixed overlay, a hand-rolled focus trap if you are lucky, no layout for a phone. An OpenCode MCP entry fixes that by giving the agent a component registry it can search, read props from and install from. The catch is the config. The OpenCode 1 docs show `mcp.<name>` with `enabled`, the OpenCode 2 docs show `mcp.servers.<name>` with `disabled`, and each is right for its own major version. This tutorial walks through OpenCode MCP server setup for both: the config shape, local versus remote entries, API key headers versus OAuth, and the prompts that turn into real components.

What we checked, on November 6, 2026: the [OpenCode 1 MCP docs](https://opencode.ai/docs/mcp-servers/), the [OpenCode 2 MCP docs](https://opencode.ai/v2/docs/mcp-servers/) and its [migration guide](https://opencode.ai/v2/docs/migrate-v1/), the JSON schema at `https://opencode.ai/config.json`, the npm registry entries of both versions, shadcn's source and our own server through curl. We did not install OpenCode for this post, so where the docs and the schema disagree we say so instead of guessing. Why an agent needs a component server at all is in our [component library MCP guide](https://wingo-ui.com/blog/component-library-mcp-guide).

## Which OpenCode version do I have, and why does it matter?

Run `opencode --version`. The two majors ship as different npm packages:

| | OpenCode 1 | OpenCode 2 |
| --- | --- | --- |
| npm package | `opencode-ai` | `@opencode/cli` |
| Latest on npm (November 6, 2026) | 1.19.5, published October 28, 2026 | 2.2.5; 2.0.0 was published September 11, 2026 |
| Commands it installs | `opencode` | `opencode` and `opencode2` |
| Where servers go | `mcp.<name>` | `mcp.servers.<name>` |
| On or off | `enabled: true` | `disabled: false` |
| Timeout | one number in ms | `{ startup, catalog, execution }` in ms |
| OAuth fields | `clientId`, `clientSecret`, `callbackPort`, `redirectUri` | `client_id`, `client_secret`, `callback_port`, `redirect_uri`, `auth_server_metadata_url` |

The [v1 CLI docs](https://opencode.ai/docs/cli/) say `opencode upgrade --major` moves you to OpenCode 2 and removes OpenCode 1. The migration guide is the part that saves you work: OpenCode 2 reads the same system, global and project files as OpenCode 1 and normalizes the v1 fields in memory, without rewriting them. So a v1 file keeps working after the upgrade. The rule it adds is to keep each server entry entirely in one shape. A v1 entry that has `enabled` but no `type` is ignored with a warning.

The schema at `opencode.ai/config.json` reflects both: `mcp.servers` is the current shape, and server names directly under `mcp` are still accepted and marked deprecated.

## Where does the OpenCode MCP config live?

In `opencode.json` or `opencode.jsonc`, in any of these places:

- **Global:** `~/.config/opencode/opencode.json`, for servers you want in every project.
- **Project:** `opencode.json` in the project root, or `.opencode/opencode.json`. This wins over the global file, and it is where you put a server the whole team should get.
- **Custom:** a path in the `OPENCODE_CONFIG` environment variable.

A component registry belongs in the project file: everyone who clones the repository gets the same server, and the agent only sees it where it is useful. Add `"$schema": "https://opencode.ai/config.json"` at the top and your editor will flag a wrong key before OpenCode does.

## How do I add a local OpenCode MCP server?

A local entry has `"type": "local"` and a `command` array: the program and its arguments as separate strings. Here is the opencode.json MCP block for our server in the OpenCode 2 shape:

```jsonc
// opencode.json (OpenCode 2)
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "wingo-ui": {
        "type": "local",
        "command": ["npx", "-y", "wingo-ui@latest", "mcp"]
      }
    }
  }
}
```

The same server for OpenCode 1:

```jsonc
// opencode.json (OpenCode 1)
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "wingo-ui": {
      "type": "local",
      "command": ["npx", "-y", "wingo-ui@latest", "mcp"],
      "enabled": true
    }
  }
}
```

The `-y` matters. Without it npx can stop to ask before it downloads a package, and nobody answers a server that OpenCode started in the background. We hit exactly that in our [Gemini CLI shadcn setup](https://wingo-ui.com/blog/gemini-cli-shadcn-mcp).

`npx wingo-ui@latest mcp` is a small stdio proxy. It forwards to `https://wingo-ui.com/mcp` and sends `WINGO_UI_API_KEY` when that is set, otherwise the key that `npx wingo-ui@latest login` saved on your machine. With neither, it connects as an anonymous client. Local entries also accept `environment` (an object of variables), `cwd` and `timeout`.

### Can the CLI write the entry for me?

Yes. In OpenCode 1, `opencode mcp add` walks you through a local or remote server. The OpenCode 2 docs show flags, with everything after `--` as the command:

```bash
opencode mcp add wingo-ui -- npx -y wingo-ui@latest mcp
opencode mcp add wingo-ui --global -- npx -y wingo-ui@latest mcp
```

The second line writes the global file instead of the project one.

## How do I add a remote server with an API key?

A remote entry has `"type": "remote"` and a `url`. Our server speaks Streamable HTTP at `https://wingo-ui.com/mcp` and takes the key as a bearer header:

```jsonc
// opencode.json (OpenCode 2)
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "wingo-ui": {
        "type": "remote",
        "url": "https://wingo-ui.com/mcp",
        "oauth": false,
        "headers": {
          "Authorization": "Bearer {env:WINGO_UI_API_KEY}"
        }
      }
    }
  }
}
```

For OpenCode 1, drop the `servers` level and add `"enabled": true`; the other keys are the same.

`{env:WINGO_UI_API_KEY}` is OpenCode's variable substitution, documented in both versions. It keeps the key out of the file, so the file can be committed. One trap: an unset variable becomes an empty string. We sent `Authorization: Bearer ` (empty) to our server with curl and it answered 200 as an anonymous client, so a missing variable shows up as "connected, but no source" instead of an error. Ask the agent to call `get_account_status` once after setup to see which account it is using.

### Why set oauth to false?

Because OpenCode tries OAuth on its own. Per the OpenCode 1 docs, a remote server that answers 401 starts an OAuth flow, with dynamic client registration when the server supports it, and the OpenCode 2 docs reserve `oauth: false` for servers that only take an API key or another header credential. Our server has no OAuth: `/.well-known/oauth-protected-resource` returns 404, and a wrong key gets a 401 with `invalid_token`. With `"oauth": false`, a bad key fails as a bad key instead of opening a browser flow that cannot finish. That is the general rule: API key servers get `oauth: false` and a header, OAuth servers get neither.

## When should I use OAuth instead?

When the server supports it, OAuth is the nicer option, since no secret sits in your shell profile. Configure only `type` and `url` (for OpenCode 1, `"oauth": {}` is the docs' explicit form), then authenticate:

```bash
opencode mcp auth sentry      # opens the browser, stores the token
opencode mcp auth list        # OpenCode 1: auth status of every OAuth-capable server
opencode mcp debug sentry     # OpenCode 1: connectivity and OAuth discovery
opencode mcp logout sentry    # removes the stored token
```

The OpenCode 2 docs list `auth` and `logout` but not `auth list` or `debug`; there, `opencode mcp auth` without a name lets you pick an OAuth-capable server. OpenCode 1 keeps the tokens in `~/.local/share/opencode/mcp-auth.json`. Servers that need a pre-registered client take `client_id`, `client_secret` and `scope` under `oauth` (camelCase in OpenCode 1).

| | Local (`npx -y wingo-ui@latest mcp`) | Remote with header | Remote with OAuth |
| --- | --- | --- | --- |
| Needs Node | Yes | No | No |
| Where the secret lives | The CLI's login store or `WINGO_UI_API_KEY` | An environment variable via `{env:...}` | OpenCode's token store |
| `oauth` key | not used | `false` | omitted, or client settings |
| Good for | Most setups | CI and machines without Node | Servers that support OAuth |

## How do I check that the server is connected?

```bash
opencode mcp list
```

It lists each configured server with its connection status (and auth status for OAuth servers). Then start a session and ask: "Call get_account_status on the wingo-ui server." Without a key it reports an anonymous client. Anonymous clients still get 10 read-only tools: `search_components`, `list_categories`, `get_component`, `get_block`, `get_design_rules`, `get_theme_tokens`, `get_install_instructions`, `check_updates`, `get_changelog` and `get_account_status`. A free account adds the source of every free item. All tools and options are on the [MCP server docs](https://wingo-ui.com/docs/mcp).

Three settings are worth knowing once it works:

- **Timeouts.** The OpenCode 1 docs give 30 seconds as the default, while the v1 field description in the JSON schema says 5 seconds. The OpenCode 2 docs give 30 seconds for `startup` and `catalog` and 12 hours for `execution`. If a slow first `npx` download times out, set `timeout` explicitly (in OpenCode 2, `timeout.startup`).
- **Tool names.** Tools are prefixed with the server name, so `wingo-ui_*` matches all of ours. OpenCode 1 can switch them off with `"tools": { "wingo-ui_*": false }` and back on per agent under `agent.<name>.tools`. OpenCode 2 uses permission rules on the same `<server>_<tool>` names.
- **Code Mode.** OpenCode 2 runs MCP servers in Code Mode by default; `"codemode": false` on a server exposes its tools directly.

## How do I add an OpenCode shadcn MCP entry?

shadcn writes it for you:

```bash
npx shadcn@latest mcp init --client opencode
```

In shadcn's source (4.23.0, November 6, 2026) that writes a project `opencode.json` with this entry:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "shadcn": {
      "type": "local",
      "command": ["npx", "shadcn@latest", "mcp"],
      "enabled": true
    }
  }
}
```

The same command installs `shadcn` as a dev dependency, so `npx` finds it locally and the missing `-y` does no harm in that project. Add `-y` when you copy the entry into a project without that dependency. It is the OpenCode 1 shape, which OpenCode 2 reads as well. If you already moved your other servers under `mcp.servers`, rewrite this one to match, with `"disabled": false` instead of `enabled`. The shadcn server searches the registries in your `components.json` and returns `npx shadcn@latest add` commands. With both servers in one project, both libraries install a `button.tsx` with different props, so say in your rules which registry owns which folder.

## What should AGENTS.md say?

It should tell the agent when to search, how to install and which tokens to use. Per the [OpenCode rules docs](https://opencode.ai/docs/rules/), OpenCode reads `AGENTS.md` from the current directory upward and falls back to `CLAUDE.md`, plus a global `~/.config/opencode/AGENTS.md`. Our CLI's `agents install` has no OpenCode target, so fetch the block it writes for Codex:

```bash
curl -s https://wingo-ui.com/agents/AGENTS.md >> AGENTS.md
```

The block sits between `<!-- wingo-ui:start v=3 -->` and `<!-- wingo-ui:end -->` markers. Its core line:

```md
- Before writing UI, use the `wingo-ui` MCP tools: `search_components` to find what exists (blocks for whole screens), `get_component` / `get_block` for props and usage, `get_design_rules` for tokens, sizes, motion and mobile rules. Never recreate a component the library has.
```

Our [AGENTS.md vs CLAUDE.md comparison](https://wingo-ui.com/blog/agents-md-vs-claude-md-react) has a fuller template.

## Which prompts get OpenCode to install real components?

Name the screen, the data and what you want reported back. The rules already send the agent to `search_components`, then `get_component`, then `npx wingo-ui@latest add`. The prompt supplies what a rules file cannot.

### Prompt 1: a delete confirmation

```text
Add a "Delete project" confirmation to app/settings with the Wingo UI dialog. Read the dialog and
button props with get_component first, install both with the CLI, use the danger tone, show a
spinner while deleting, and check the dialog at 390px.
```

The [Dialog](https://wingo-ui.com/components/dialog) and the [Button](https://wingo-ui.com/components/button) are both free, and the core of a good result is short:

```tsx
// app/settings/delete-project-dialog.tsx
"use client";

import { Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogClose } from "@/components/ui/dialog";

type Props = { name: string; deleting: boolean; onDelete: () => void };

export function DeleteProjectDialog({ name, deleting, onDelete }: Props) {
  return (
    <Dialog
      trigger={
        <Button variant="soft" tone="danger" leftIcon={<Trash2 />}>
          Delete project
        </Button>
      }
      title={`Delete ${name}?`}
      description="This removes the project and its deployments. You cannot undo it."
      icon={<Trash2 />}
      tone="danger"
      footer={
        <>
          <DialogClose asChild>
            <Button variant="soft">Cancel</Button>
          </DialogClose>
          <Button tone="danger" loading={deleting} loadingText="Deleting" onClick={onDelete}>
            Delete
          </Button>
        </>
      }
    />
  );
}
```

Below 768px the same component opens as a bottom sheet with full-width 48px actions, which is the part a model writing from memory skips.

> Live demo (Dialog): Open the dialog, then narrow the window below 768px: it reopens as a bottom sheet you can drag down. Try it at [Dialog](https://wingo-ui.com/components/dialog) and install it with `npx wingo-ui@latest add dialog`.

### Prompt 2: a sign-in screen

```text
Build /login from the Wingo UI login-page block, card layout, password and 6 digit email code, no
social buttons. Post to our /api/auth routes and go to /dashboard on success. Replace every
Northwind string in the labels with Harbor and list each handler you left on its sample default.
```

The [Login Page](https://wingo-ui.com/components/login-page) ships sample handlers so its demo runs, and its default labels name Northwind CRM. That is why the prompt asks for a report: without `onVerifyCode`, the code view accepts 123456 for anyone. A good result passes `layout="card"`, `methods={["password", "otp"]}`, `showProviders={false}`, `labels` with your app name, and real `onSignIn`, `onSendCode`, `onVerifyCode` and `onSuccess` handlers.

> Live demo (Login Page): Type any email and a password shorter than 8 characters to see the error alert, then open Forgot password: the email carries over. Try it at [Login Page](https://wingo-ui.com/components/login-page) and install it with `npx wingo-ui@latest add login-page`.

### Prompt 3: replace hand-written spinners

```text
Find every button in app/account that renders its own spinner while saving and replace it with the
Wingo UI button. Read its props with get_component first and show me the diff.
```

The Button's `loading` and `loadingText` props replace the spinner markup and the `disabled` juggling, and `onClick` is not called while it is loading.

## What goes wrong, and how do I fix it?

- **The server never shows up.** A v1 entry without `type`, or an entry that mixes `enabled` and `disabled`. Pick one shape per entry and run `opencode mcp list` again.
- **A local server fails to start.** Usually npx waiting for an install answer: add `-y` to `command`. A slow first download can also hit the timeout.
- **A browser window opens for an API key server.** `oauth` is missing. Set `"oauth": false` and send the key in `headers`.
- **Connected, but no source.** The key variable is unset, so the header went out empty. Export it in the shell that starts OpenCode, or use the local entry after `npx wingo-ui@latest login` (a free account needs only an email).
- **Pro blocks with a free key.** `get_block` still returns props and usage for `login-page`, and `npx wingo-ui@latest add login-page` stops because the block is part of Pro. The Button and Dialog are free; the Login Page is Pro ($8 a month, $80 a year or $150 once, on the [pricing page](https://wingo-ui.com/pricing)).

## Where should I start?

Create a free account, run `npx wingo-ui@latest login`, add the local entry to your project's `opencode.json`, append the `AGENTS.md` block and try prompt 1 on the free Dialog and Button. The same setup for other agents is in our [Antigravity MCP setup](https://wingo-ui.com/blog/antigravity-mcp), [Codex MCP setup](https://wingo-ui.com/blog/codex-mcp-setup-react-ui) and [Claude Code shadcn MCP setup](https://wingo-ui.com/blog/claude-code-shadcn-mcp-setup), and the rest of the series is under [Building UI with AI agents](https://wingo-ui.com/blog/category/ai-agents).

## Components in this post

- [Button](https://wingo-ui.com/components/button) (Free): The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps. Install: `npx wingo-ui@latest add button`
- [Dialog](https://wingo-ui.com/components/dialog) (Free): A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling. Install: `npx wingo-ui@latest add dialog`
- [Login Page](https://wingo-ui.com/components/login-page) (Pro): A React login page with password, magic link or code sign-in, Google, Apple and Microsoft buttons, two-step codes and password recovery. Install: `npx wingo-ui@latest add login-page`

## FAQ

### Where does OpenCode look for MCP servers?

In the mcp key of opencode.json or opencode.jsonc: globally in ~/.config/opencode/, per project in the project root or in .opencode/. Project config wins over the global file, and both OpenCode 1 and 2 read the same locations.

### What changed in the OpenCode 2 MCP config?

Servers moved from mcp.<name> to mcp.servers.<name>, enabled became the inverse disabled, timeout became an object with startup, catalog and execution, and OAuth fields became snake_case (client_id instead of clientId). OpenCode 2 normalizes the old shape in memory, so a v1 file keeps working.

### Should a remote MCP server in OpenCode use OAuth or an API key header?

Use OAuth when the server supports it, because OpenCode stores the token for you after opencode mcp auth. When the server takes an API key, put it in headers with {env:VAR} and set oauth to false, so a rejected key does not start an OAuth flow the server cannot finish.

### Does the shadcn MCP server work with OpenCode?

Yes. npx shadcn@latest mcp init --client opencode writes a project opencode.json with a local shadcn entry in the OpenCode 1 shape (enabled: true), which OpenCode 2 also reads. The command array in shadcn 4.23.0 has no -y, which is fine because mcp init also installs shadcn as a dev dependency; add -y if you copy the entry into another project.

### How do I check that an OpenCode MCP server is connected?

Run opencode mcp list, which lists each configured server with its connection status; in OpenCode 1, opencode mcp debug <name> also tests OAuth. Then ask a question only the server can answer, such as calling get_account_status on the wingo-ui server.

---

Source: https://wingo-ui.com/blog/opencode-mcp
