# Codex MCP Setup: Build React UI With Real Components

> Codex MCP setup for React UI, tested on Codex CLI 0.162.1: the config.toml entry, the key variable, AGENTS.md rules, prompts that use real blocks, and fixes.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 13, 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/codex-mcp-setup-react-ui

## TL;DR

Run npx wingo-ui@latest login, then npx wingo-ui@latest agents install codex. It adds an [mcp_servers.wingo-ui] table to ~/.codex/config.toml and a short rules block to ~/.codex/AGENTS.md; with WINGO_UI_API_KEY exported, the table uses url and bearer_token_env_var, otherwise it runs the local npx -y wingo-ui@latest mcp proxy. Restart Codex, check the server with codex mcp list or /mcp, and ask for screens by block name so Codex calls search_components and get_block before it writes code.

OpenAI Codex writes React fast, and left alone it writes the components it remembers: a settings page from scratch, a modal without a phone layout, a button with a hand-rolled spinner. A Codex MCP connection to your component library fixes that, because Codex can search the real catalog, read props with their defaults and install the source with a CLI before it writes a line. This tutorial sets that up end to end with our library, Wingo UI: the key, the `config.toml` entry, the rules file, the restart and the check, three prompts that produce whole screens from real blocks, and the errors you are likely to hit.

We tested it on October 13, 2026 with Codex CLI 0.162.1, our CLI 1.0.3 and Wingo UI release 1.8.0, and checked the config syntax against OpenAI's [Codex MCP docs](https://developers.openai.com/codex/mcp) and [config reference](https://developers.openai.com/codex/config-reference) on the same day. What an MCP server for components is and why agents need one is covered in our [component library MCP guide](https://wingo-ui.com/blog/component-library-mcp-guide); this post is the Codex half of it, in detail.

## What does a Codex MCP server setup need?

Four pieces, and one command writes the two files:

| Piece | Where | Who writes it |
| --- | --- | --- |
| An API key | `~/.config/wingo-ui` or `WINGO_UI_API_KEY` | `npx wingo-ui@latest login`, or your account page |
| The MCP server entry | `~/.codex/config.toml`, table `[mcp_servers.wingo-ui]` | `npx wingo-ui@latest agents install codex` |
| The rules | `~/.codex/AGENTS.md`, a marked block | the same command |
| A restart | Codex reads both files when it starts | you |

## Step 1: How do I get a key?

Sign the CLI in once per computer:

```bash
npx wingo-ui@latest login
```

It opens the browser to approve the device and saves an API key in `~/.config/wingo-ui`. A free account needs only an email. The key decides what Codex receives:

- **No key:** search, props, docs, design rules and tokens all answer, with no source. We called `get_account_status` without one and the server said exactly that.
- **Free key:** adds the source of the 75 free items, the [Button](https://wingo-ui.com/components/button) among them.
- **Pro key:** adds the source of every item, including blocks like the [Settings Page](https://wingo-ui.com/components/settings-page) and [Login Page](https://wingo-ui.com/components/login-page) used below.

For the HTTP setup in step 3 you also want the key as a value: create one for Codex on your account's API keys page, so you can revoke it on its own later.

## Step 2: What does agents install codex write?

Run it in the project:

```bash
npx wingo-ui@latest agents install codex
```

Without `WINGO_UI_API_KEY` in the environment, on a clean machine, it printed these lines (plus a note about the key):

```text
Coding agents (Codex; MCP over the local stdio proxy)
  created    ~/.codex/AGENTS.md
  created    ~/.codex/config.toml
```

The default is your user config on purpose. OpenAI's config reference says Codex "loads project-scoped config files only when you trust the project", and untrusted projects skip the whole `.codex/` folder. A server in your home folder works in every project, trusted or not.

For a repository your team shares, `--scope project` writes `.codex/config.toml` and a marked block in the project's `AGENTS.md` instead. In our test project, create-next-app had already written an `AGENTS.md`; the CLI appended its block and kept the rest. Running the command again changes nothing, and it keeps other servers in the file.

### Should Codex use HTTP or the stdio proxy?

The CLI picks HTTP when `WINGO_UI_API_KEY` is set and the proxy otherwise; `--mcp http` or `--mcp stdio` picks for you. This is the Codex config.toml mcp_servers table each one writes:

```toml
# HTTP: Codex calls the server directly and sends $WINGO_UI_API_KEY as a bearer token
[mcp_servers.wingo-ui]
url = "https://wingo-ui.com/mcp"
bearer_token_env_var = "WINGO_UI_API_KEY"
```

```toml
# stdio: Codex starts a local proxy that uses the key saved by wingo-ui login
[mcp_servers.wingo-ui]
command = "npx"
args = ["-y", "wingo-ui@latest", "mcp"]
```

`bearer_token_env_var` holds the name of a variable, never the key itself, so the file is safe to sync or share. HTTP is one less process and starts instantly. The proxy needs no environment setup: if you already ran `login`, it works. Pick the proxy if you start Codex from an editor or app that may not load your shell profile.

## Step 3: How do I export the key for Codex?

HTTP mode only. Codex reads the variable when it starts, so it has to be in the environment of the shell that launches it. Put it in your shell profile:

```bash
# ~/.zshrc or ~/.bashrc
export WINGO_UI_API_KEY="wui_..."
```

Open a new terminal, and check that the variable is there before you start Codex: `echo $WINGO_UI_API_KEY | cut -c1-4` should print `wui_`.

## Step 4: How do I check the connection?

Restart Codex, then check from the shell. In our test project `codex mcp list` printed:

```text
Name      Url                       Bearer Token Env Var  Status   Auth
wingo-ui  https://wingo-ui.com/mcp  WINGO_UI_API_KEY      enabled  Bearer token
```

`codex mcp get wingo-ui` shows the transport (`streamable_http` for HTTP). Inside a Codex session, type `/mcp` to see the active servers. Then ask one question that only the server can answer:

```text
Call get_account_status on the wingo-ui MCP server and tell me what it says.
```

If the answer mentions your plan, the key reached the server. If it says "Anonymous", Codex connected without a key: check step 3.

## What do the AGENTS.md rules tell Codex?

When to call which tool, how to install, and what never to do. This is the block the CLI wrote, between version markers that `npx wingo-ui@latest agents update` rewrites later:

```md
<!-- wingo-ui:start v=2 -->
## Wingo UI

This project builds its UI with Wingo UI (React 19 + Tailwind CSS v4 components installed as source, see `wingo-ui.json`).

- 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.
- Install with `npx wingo-ui@latest add <slug...>`. At the start of UI work run `check_updates` (or `npx wingo-ui@latest outdated`) and mention updates; update with `npx wingo-ui@latest update`.
- Semantic tokens only (`bg-background`, `bg-surface`, `text-muted-foreground`, `border-border`), configure components through props, keep `cn()`, `data-slot` and license headers in copied files. Check 390px and 1440px, light and dark.
- No MCP tools available? The CLI has `search`, `info`, `add`, `outdated`, `update`; the docs for models are at https://wingo-ui.com/llms.txt.
<!-- wingo-ui:end -->
```

How Codex finds it, from OpenAI's [AGENTS.md guide](https://developers.openai.com/codex/guides/agents-md): first `~/.codex/AGENTS.md` (or `AGENTS.override.md`), then one file per folder from the project root down to where you started Codex, joined in that order up to `project_doc_max_bytes`, 32 KiB by default. Files closer to your working folder come later, so a project's own rules can refine the global block.

That order is the one trade-off of the default setup: the global block says "This project builds its UI with Wingo UI" in every repository on your machine. If you also work on projects that use something else, install with `--scope project` there, or add a line to those projects' `AGENTS.md` that says which library they use. If you want a fuller Codex AGENTS.md React template, with commands, tokens and the checks for every screen, our [AGENTS.md vs CLAUDE.md comparison](https://wingo-ui.com/blog/agents-md-vs-claude-md-react) has one.

## Which prompts get OpenAI Codex to build React UI from real components?

Prompts that name the block, say where its data comes from and ask for a list of what is still simulated. That holds for any OpenAI Codex React UI task, with any library. Our blocks ship working sample handlers so their demos run, and every handler you do not pass keeps its sample behavior. Here are three, with the tools the rules and server instructions lead Codex to call. We called each of those tools on the server directly to check its answer; how often a given model follows them varies.

| Prompt | Tools Codex should call | Then |
| --- | --- | --- |
| Settings screen | `search_components` (kind block), `get_block` for `settings-page`, `get_design_rules` for forms | `npx wingo-ui@latest add settings-page` |
| Sign-in | `search_components`, `get_block` for `login-page` | `npx wingo-ui@latest add login-page` |
| Component swap | `check_updates`, `search_components`, `get_component` for `button` | `npx wingo-ui@latest add button` |

### Prompt 1: a settings screen from settings-page

```text
Build app/settings with the Wingo UI settings-page block. Read its props with get_block first.
Keep the Profile, Account and Notifications sections only. Load the profile and account from
getSessionUser() in lib/session.ts on the server, and save each section with PATCH /api/settings/<section>.
Our product is called Harbor: replace the sample product name in every label you show.
List every on* handler or verify* function you left on its sample default.
```

The block's section ids are its own (`profil`, `cont`, `notificari`, `facturare`, `securitate`, `pericol`); the labels on screen are English. A good run ends with a client component like this, which we type-checked against release 1.8.0:

```tsx
// app/settings/settings-screen.tsx
"use client";

import Link from "next/link";
import {
  SettingsPage,
  type SettingsAccount,
  type SettingsFormSection,
  type SettingsProfile,
  type SettingsSaveValues,
} from "@/components/blocks/settings-page";

async function patch(path: string, body: unknown) {
  const res = await fetch(path, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  // a rejection keeps the draft and shows this message in the save bar
  if (!res.ok) throw new Error("Your changes were not saved. Try again.");
}

type SettingsScreenProps = { profile: SettingsProfile; account: SettingsAccount };

export function SettingsScreen({ profile, account }: SettingsScreenProps) {
  return (
    <SettingsPage
      sections={["profil", "cont", "notificari"]}
      profile={profile}
      account={account}
      onSave={(section: SettingsFormSection, values: SettingsSaveValues[SettingsFormSection]) =>
        patch(`/api/settings/${section}`, values)
      }
      onPreferenceChange={(preference, value) => patch("/api/settings/preferences", { preference, value })}
      labels={{
        photoDescription: "Shows next to your name everywhere in Harbor.",
        workspaceAddressDescription: "Your teammates open Harbor at this address.",
        workspaceDomain: "harbor.app/",
      }}
      linkComponent={(props) => <Link {...props} />}
    />
  );
}
```

The list Codex should hand back includes `onUpload` (the photo upload), `onEmailChange` and `verifyEmailCode` (the change-email dialog, where the code 123456 passes until you wire it), and `checkWorkspaceAddress`. In the demo below, a save fails on purpose so you can see what the user sees when your API rejects one.

> Live demo (Settings Page): Open Profile, edit the full name and press Save: this demo drops the first save, so the bar shows the error and keeps your edit, and the next Save goes through. Try it at [Settings Page](https://wingo-ui.com/components/settings-page) and install it with `npx wingo-ui@latest add settings-page`.

### Prompt 2: a sign-in from login-page

```text
Build /login from the Wingo UI login-page block, centered layout, no social providers.
Sign in with a password or a 6 digit email code through our /api/auth routes, go to /dashboard
on success, and replace every Northwind string in the labels with Harbor.
List every handler you left on its simulated default.
```

The sample brand matters here: the block's default labels name Northwind CRM in the app name and the sign-in description and Northwind Software in the copyright line, and its default logo is the Northwind mark. The result we expect:

```tsx
// app/login/login-screen.tsx
"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { LoginPage, type SignInValues, type VerifyCodeContext } from "@/components/blocks/login-page";

async function post(path: string, body: unknown) {
  const res = await fetch(path, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const data = (await res.json().catch(() => null)) as { message?: string } | null;
    // the page shows the message of a rejected promise in its error alert
    throw new Error(data?.message ?? "Something went wrong. Try again.");
  }
}

export function LoginScreen() {
  const router = useRouter();
  return (
    <LoginPage
      layout="centered"
      methods={["password", "otp"]}
      showProviders={false}
      logo={<span className="text-lg font-semibold">Harbor</span>}
      labels={{
        appName: "Harbor",
        signInDescription: "Sign in to your Harbor account to continue.",
        copyright: "© 2026 Harbor Inc.",
      }}
      onSignIn={(values: SignInValues) => post("/api/auth/sign-in", values)}
      onSendCode={(email) => post("/api/auth/send-code", { email })}
      onVerifyCode={(code: string, context: VerifyCodeContext) => post("/api/auth/verify-code", { code, ...context })}
      onForgot={(email) => post("/api/auth/forgot", { email })}
      onReset={(password) => post("/api/auth/reset", { password })}
      onSuccess={() => router.push("/dashboard")}
      registerHref="/signup"
      linkComponent={(props) => <Link {...props} />}
    />
  );
}
```

Without `onVerifyCode`, the code view accepts 123456 for anyone, which is the line in the agent's report you most want to see. With the methods above and the providers hidden, the list should come back empty.

> Live demo (Login Page): Sign in with any email and a password shorter than 8 characters to see the error alert, then open Forgot password: the email you typed 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: swap a hand-written component

```text
Run check_updates for the items in wingo-ui.json first. Then find every button in app/account
that renders its own spinner while saving, and replace it with the Wingo UI button.
Use get_component for its props, install it with the CLI, and show me the diff.
```

The point is `get_component`: our Button has a `loading` prop, so the hand-rolled spinner and the `disabled` juggling go away:

```tsx
import { Button } from "@/components/ui/button";

export function SaveButton({ saving, onSave }: { saving: boolean; onSave: () => void }) {
  return (
    <Button tone="brand" loading={saving} loadingText="Saving" onClick={onSave}>
      Save changes
    </Button>
  );
}
```

While `saving` is true the label swaps for a spinner and "Saving", the width holds, clicks are ignored and `aria-busy` is set.

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

- **The server is missing in a trusted project.** You installed with `--scope project`, and Codex does not trust the folder. In our test, `codex mcp list` printed "No MCP servers configured yet" until we marked the folder as trusted. Trust lives in `~/.codex/config.toml` as `projects.<path>.trust_level`; we added `trust_level = "trusted"` under a `[projects."<path>"]` table and the server appeared. Trust the project when Codex asks, or install with the default user scope.
- **The key variable is missing.** Codex has no token to send for the HTTP entry. Check with the `echo` line from step 3 in the shell you start Codex from, export `WINGO_UI_API_KEY` in your profile, open a new terminal and restart Codex. An editor or desktop app may not load your shell profile; switch to the proxy with `npx wingo-ui@latest agents install codex --mcp stdio`.
- **The stdio proxy times out on first start.** `npx` downloads the package the first time, and Codex waits 10 seconds by default (`startup_timeout_sec`). Add `startup_timeout_sec = 30` to the `[mcp_servers.wingo-ui]` table, or run `npx -y wingo-ui@latest --version` once beforehand.
- **A free key and a Pro block.** `get_block` still returns props and usage for `settings-page`, but no source, and `npx wingo-ui@latest add settings-page` stops with "Settings Page is part of Wingo UI Pro". Ask Codex to build from free components instead, or upgrade.
- **Codex writes UI without calling the tools.** Check that the rules block is in an `AGENTS.md` Codex reads (`npx wingo-ui@latest agents status` tells you), and name the block in the prompt.

## Where should I start?

Run `npx wingo-ui@latest login`, then `npx wingo-ui@latest agents install codex`, restart Codex and try prompt 3 on a free component first. The [MCP server docs](https://wingo-ui.com/docs/mcp) list every tool, and the [coding agents docs](https://wingo-ui.com/docs/agents) cover the other agents and the `--scope` and `--mcp` options. The Settings Page and Login Page blocks are part of Wingo UI Pro, on the [pricing page](https://wingo-ui.com/pricing). Everything we ship for coding agents is on one page, [Wingo UI for AI agents](https://wingo-ui.com/ai), and more setups like this one live under [Building UI with AI agents](https://wingo-ui.com/blog/category/ai-agents), including the [Claude Code shadcn MCP setup](https://wingo-ui.com/blog/claude-code-shadcn-mcp-setup) for the same job in Claude Code.

## 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`
- [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`
- [Settings Page](https://wingo-ui.com/components/settings-page) (Pro): A React account settings page for a back office: profile, account, notifications, billing, security and a danger zone, with a save bar. Install: `npx wingo-ui@latest add settings-page`

## FAQ

### Where does Codex store MCP servers?

In config.toml: ~/.codex/config.toml for every project, or .codex/config.toml inside a project. OpenAI's docs say Codex loads the project file only when you trust the project, which is why our CLI writes to the user file by default.

### How do I pass an API key to a remote MCP server in Codex?

Set bearer_token_env_var to the name of an environment variable, such as WINGO_UI_API_KEY, and export that variable in the shell that starts Codex. Codex sends its value as a bearer token in the Authorization header, so the key never goes in the config file.

### Why does Codex not see my MCP server?

Usually the server sits in .codex/config.toml of a project Codex does not trust, or Codex was not restarted after the change. Run codex mcp list in the project folder: if the server is missing there, Codex is not reading the file.

### Does Codex read AGENTS.md from my home folder?

Yes. Codex reads ~/.codex/AGENTS.md first, then AGENTS.md files from the project root down to the current folder, and joins them up to 32 KiB by default (project_doc_max_bytes).

### Can Codex install Pro components with a free key?

No. A free key returns the source of free items; Pro blocks such as the Settings Page still return their props and docs, but the source and npx wingo-ui@latest add need a Pro account.

---

Source: https://wingo-ui.com/blog/codex-mcp-setup-react-ui
