# Claude Code shadcn Setup: MCP Server, Skill and CLAUDE.md

> Claude Code shadcn setup, tested on shadcn 4.21.4: the MCP server, the skill, a second registry in its own folder, a CLAUDE.md block and prompts that work.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 9, 2026
- Category: [Building UI with AI agents](https://wingo-ui.com/blog/category/ai-agents)
- Reading time: 12 min
- Canonical: https://wingo-ui.com/blog/claude-code-shadcn-mcp-setup

## TL;DR

Run npx shadcn@latest mcp init --client claude to add the shadcn MCP server to .mcp.json, install the shadcn skill with npx skills add shadcn/ui, and list every extra registry in components.json. Then add a short block to CLAUDE.md that says which registry owns which folder and which CLI installs and updates it, and name the registry in every prompt. The server finds components, the skill and the rules steer Claude, and the CLI writes the files.

Claude Code already knows shadcn/ui from its training data, and that is the problem. It writes the API it remembers, which may not match the files in your `components/ui`, and it has never seen the registries you added to `components.json` last month. A Claude Code shadcn setup that holds up has five parts: the shadcn MCP server so Claude can search real registries, the shadcn skill for project context, the registry list in `components.json`, a short rules file that says which registry owns which folder, and a CLI that writes the files. This tutorial sets up all five, adds a second registry, and ends with prompts shaped to get the right component installed.

Everything below was run in October 2026 against shadcn 4.21.4, the `skills` CLI 1.7.1 and our own registry, Wingo UI 1.7.0. What we say about Claude Code itself comes from its docs as of the same month. For the wider picture, read our [component library MCP guide](https://wingo-ui.com/blog/component-library-mcp-guide) first.

## What does a Claude Code shadcn setup need?

A server that searches registries, instructions that say when to use it, a list of registries and a CLI that installs:

| Piece | Job | How you add it |
| --- | --- | --- |
| shadcn MCP server | Searches every registry in `components.json`, returns add commands | `npx shadcn@latest mcp init --client claude` |
| shadcn skill | Project config and shadcn/ui composition rules | `npx skills add shadcn/ui` |
| `components.json` | The registries to search, with auth headers | Edit by hand |
| `CLAUDE.md` block | Which registry owns which folder and which CLI installs it | Six lines by hand |
| The CLI | Writes files, installs npm packages | `npx shadcn@latest add` or a library's own |

A library's own MCP server is an optional sixth piece: the shadcn server returned no props in our tests, and a library server can. Ours joins shadcn's in the same `.mcp.json` through `npx wingo-ui@latest agents install claude`.

## How do I add the shadcn MCP server to Claude Code?

Run one command in the project root, restart Claude Code and check `/mcp`:

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

It writes this entry to `.mcp.json`, then installs `shadcn` as a dev dependency:

```json
{
  "mcpServers": {
    "shadcn": {
      "command": "npx",
      "args": ["shadcn@latest", "mcp"]
    }
  }
}
```

`.mcp.json` is Claude Code's project scope: commit it and the team gets the server, after each person approves it once. For a setup only you use, `claude mcp add shadcn -- npx shadcn@latest mcp` adds it in local scope, the default, instead.

### Which tools does the shadcn MCP server give Claude?

Once the Claude Code shadcn MCP connection is up, Claude gets seven read-only tools. This is the list the server returned in 4.21.4:

- `get_project_registries`: the registries in `components.json`.
- `list_items_in_registries` and `search_items_in_registries`: browse or fuzzy-search, with paging.
- `view_items_in_registries`: name, description, type, file count and npm dependencies.
- `get_item_examples_from_registries`: demo code, found by names like `dialog-demo`.
- `get_add_command_for_items`: the `npx shadcn@latest add` command for items.
- `get_audit_checklist`: a checklist for after generating code.

Three details shape how you prompt it. The server never writes files: it hands Claude an add command, and every install is a Bash call you can read first. The server sends no instructions, and Claude Code's tool search loads only tool names and server instructions when a session starts, so the skill and your rules file have to say when to use it. And `view_items_in_registries` returned neither props nor source for `@shadcn/button` or our dialog; examples exist only where a registry publishes `-demo` items, which shadcn/ui does and we do not yet. For a third-party item, Claude learns the API from the installed file or the library's own docs.

One bug in 4.21.4: search results print each add command as `[object Promise]`. `get_add_command_for_items` returns the real one, `npx shadcn@latest add @shadcn/button @wingo-ui/dialog` in our test.

## How do I add a second registry, private or Pro?

Add it under `registries` in `components.json`. A public registry needs only its URL template:

```json
{
  "registries": {
    "@wingo-ui": "https://wingo-ui.com/r/{name}.json"
  }
}
```

A shadcn private registry takes headers, with the secret in an environment variable:

```json
{
  "registries": {
    "@wingo-ui": {
      "url": "https://wingo-ui.com/r/{name}.json",
      "headers": { "Authorization": "Bearer ${WINGO_UI_API_KEY}" }
    }
  }
}
```

The shadcn CLI reads that variable from the environment or from `.env.local`; we confirmed both. When the key is set, `agents install` writes our own MCP entry in `.mcp.json` with the same `${WINGO_UI_API_KEY}`, and Claude Code expands it from the environment it started in. Export the key in your shell profile and both servers see it.

Use the header form only when the variable is set. Without it, every call that touches `@wingo-ui` fails with `MISSING_ENV_VARS`: a search across `@shadcn` and `@wingo-ui` returned only that error, with no shadcn results either. Free Wingo UI items need no key, so keep the plain URL until you have one.

Then one search covers both registries. This is the real answer to `search_items_in_registries` for "login" with a limit of 4, the broken add-command lines removed:

```text
Found 173 items matching "login" in registries @shadcn, @wingo-ui:

Showing items 1-4 of 173:

- login-page (registry:block) - The sign-in screen of Northwind CRM: password, email link or code, Google / Apple / Microsoft, two-step codes and the whole password recovery. [@wingo-ui]
- login-01 (registry:block) - A simple login form. [@shadcn]
- login-05 (registry:block) - A simple email-only login page. [@shadcn]
- login-02 (registry:block) - A two column login page with a cover image. [@shadcn]
```

173 matches for one word is fuzzy matching at work (`@shadcn` alone returned 7), so ask for a small limit.

### What happens when two registries both write components/ui/button.tsx?

Neither CLI replaces the other's file silently, and either answer breaks something. shadcn/ui and many shadcn-style libraries install into `components/ui` with the same file names and different APIs. When we added our [Dialog](https://wingo-ui.com/components/dialog) to a project that already had the shadcn button, our CLI reported a conflict on `components/ui/button.tsx`, kept the shadcn file and still wrote the dialog and drawer next to it; the shadcn CLI asks before it overwrites. Keeping the shadcn button breaks the type-check, because our dialog and drawer render their close buttons with `radius="full"`, a prop shadcn's Button does not have. Replacing it breaks every `variant="default"`, `"secondary"` or `"destructive"` in your app, because our [Button](https://wingo-ui.com/components/button) variants are `solid`, `soft`, `outline`, `ghost` and `link` (destructive is `tone="danger"`).

Give the second library its own folder. Our CLI reads its paths from `wingo-ui.json`, so after `npx wingo-ui@latest init`, change the `ui` alias and keep the rest:

```json
{
  "aliases": {
    "ui": "@/components/wingo",
    "blocks": "@/components/blocks",
    "hooks": "@/hooks",
    "lib": "@/lib",
    "utils": "@/lib/utils"
  }
}
```

`npx wingo-ui@latest add dialog` then wrote `button.tsx`, `drawer.tsx` and `dialog.tsx` into `components/wingo` beside the untouched shadcn button and rewrote their imports to `@/components/wingo/...`. `init` had already merged our design tokens into `app/globals.css`, and `add` appended the drawer's transitions. That is the second reason to install our items with our CLI: the shadcn-format JSON at `/r/<slug>.json` carries no tokens, so in a project without them, `z-modal` and `shadow-modal` on a shadcn-installed dialog resolve to nothing.

## Should I install the Claude Code shadcn skill too?

Yes, if your project uses shadcn/ui components; if it also uses another library, scope the skill in your rules file. The skill is the official instruction set from the shadcn/ui repository:

```bash
npx skills add shadcn/ui --agent claude-code
```

The `skills` CLI wrote two skills into `.claude/skills`, `shadcn` and `migrate-radix-to-base`, and recorded both in `skills-lock.json`. Claude Code keeps only a skill's description in context until the skill is used, so the 19.5 KB `SKILL.md` costs little until a UI task starts. Its description matches any project with a `components.json`, so expect it on most UI work.

When it loads, it runs `npx shadcn@latest info --json` and puts your framework, Tailwind version, aliases, base library and installed components in front of Claude. Its `allowed-tools` line also lets Claude run any `npx shadcn@latest` command without asking for the rest of that turn, `add --overwrite` included. Three of its rules matter before you prompt:

- **Name the registry.** Ask for "a login block" without one and the skill tells Claude to ask you which registry to use.
- **Docs first.** Claude runs `npx shadcn@latest docs <component>` before using one. That command only knows shadcn/ui: `docs dialog` returned links to the shadcn/ui and Radix Dialog docs, and `docs @wingo-ui/dialog` answered that the component was not found in the shadcn registry.
- **shadcn/ui composition.** Forms use `FieldGroup` and `Field`, icons in buttons take `data-icon`, and since shadcn's Button has no loading prop, Claude composes a `Spinner` and `disabled` by hand.

All three are right for shadcn/ui files, and the last two are wrong for other registries. Our Button has a `loading` prop that shows a spinner in place of the label, keeps the width, sets `aria-busy` and ignores clicks, so a hand-built spinner duplicates it. In the demo below, pressing "Save changes" swaps the label for a spinner and "Saving" while the button keeps its width.

> Live demo (Button): Click Save changes: the spinner takes the label's place, the width holds and the button ignores clicks until it settles. Try it at [Button](https://wingo-ui.com/components/button) and install it with `npx wingo-ui@latest add button`.

## What should CLAUDE.md say?

Who owns which folder, which tools to search with, which CLI installs and updates, and where the shadcn skill's rules stop. Claude Code loads `CLAUDE.md` at the start of every session, so this block is always in context:

```md
## UI components

- Two registries. `components/ui` is shadcn/ui (`@shadcn` in components.json). `components/wingo` is Wingo UI (versions in wingo-ui.json); import it from `@/components/wingo/<name>`.
- Before writing UI, search: the `shadcn` MCP tools for @shadcn and other registries, the `wingo-ui` MCP tools for Wingo UI. Never hand-write a component a registry has.
- Install shadcn items with `npx shadcn@latest add @shadcn/<name>` and Wingo UI items with `npx wingo-ui@latest add <slug>`. Never install a Wingo UI item with the shadcn CLI.
- Read the API of the file you import. The shadcn skill's rules and `shadcn docs` apply to `components/ui` only; for Wingo UI items call `get_component` and configure them through their props.
- Semantic tokens only, no hex colors. Check 390px and 1440px wide, in light and dark.
- Never pass `--overwrite` without asking me. Before any update, show me the diff or the release notes.
```

The first line answers what no tool can: which folder belongs to whom. It matters twice in this layout, because our own skill, which `agents install` writes, assumes the default folder and tells Claude that imports use `@/components/ui/<name>`. The fourth line keeps shadcn/ui patterns out of files with a different API. The last repeats the skill's own `--overwrite` rule, because the skill's permission grant covers that flag. On shadcn/ui alone, keep the search, install and overwrite lines. If Codex or Cursor work in the same repository, put these lines in AGENTS.md and import it from CLAUDE.md; our [AGENTS.md vs CLAUDE.md comparison](https://wingo-ui.com/blog/agents-md-vs-claude-md-react) explains which file each agent reads.

Allow the read-only tools in `.claude/settings.json` and leave installs on ask:

```json
{
  "permissions": {
    "allow": [
      "mcp__shadcn",
      "mcp__wingo-ui",
      "Bash(npx shadcn@latest search *)",
      "Bash(npx shadcn@latest view *)",
      "Bash(npx wingo-ui@latest outdated)"
    ]
  }
}
```

## Which prompts get Claude to install the right component?

The ones that name the registry, ask for a search before an install, and say what to check at the end. Five prompts built that way:

1. **Find before you build.** "Search @shadcn and @wingo-ui for a modal that becomes a bottom sheet on phones. List the candidates with their registry and install command, and install nothing yet." Naming both registries satisfies the skill's rule, and "install nothing yet" keeps the first step read-only.
2. **Build on one component's real API.** "Add the Wingo UI dialog and input with the Wingo CLI, read their props with get_component, then build a profile dialog in app/settings/profile-dialog.tsx with a name field, Cancel and Save, a loading state on Save and an error if saving fails." Naming the source of the API steers Claude away from the shadcn/ui Dialog it remembers.
3. **Start screens from a block.** "Build /login from the Wingo UI login-page block. Wire onSignIn and onVerifyCode to our /api/auth routes, hide the social providers, and list every on* handler you left on its simulated default." The [Login Page](https://wingo-ui.com/components/login-page) ships simulated handlers so its demo works, and each one you do not pass stays simulated: without `onVerifyCode`, the code views accept `123456`.
4. **Ask for updates as a report.** "Check my Wingo UI components and my shadcn/ui button for upstream changes. Show me the notes and the diff, and change nothing."
5. **End with a check.** "Run get_audit_checklist, then type-check and lint the files you touched." The checklist is six generic lines; the type-check is what catches a wrong prop, such as `variant="destructive"` on our Button.

For prompt 2, this is the file a good run ends with, on the real APIs of the [Dialog](https://wingo-ui.com/components/dialog), the Button and the [Input](https://wingo-ui.com/components/input). We type-checked it against release 1.7.0:

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

import { useState } from "react";
import { Pencil } from "lucide-react";
// next to shadcn/ui these imports read @/components/wingo/...
import { Button } from "@/components/ui/button";
import { Dialog, DialogClose } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";

type ProfileDialogProps = {
  initialName: string;
  onSave: (name: string) => Promise<void>;
};

export function ProfileDialog({ initialName, onSave }: ProfileDialogProps) {
  const [open, setOpen] = useState(false);
  const [name, setName] = useState(initialName);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");

  function changeOpen(next: boolean) {
    // start from the saved name every time it opens
    if (next) {
      setName(initialName);
      setError("");
    }
    setOpen(next);
  }

  async function save() {
    setSaving(true);
    setError("");
    try {
      await onSave(name.trim());
      setOpen(false);
    } catch {
      setError("Your name was not saved. Try again.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <Dialog
      open={open}
      onOpenChange={changeOpen}
      trigger={
        <Button variant="soft" leftIcon={<Pencil />}>
          Edit profile
        </Button>
      }
      title="Edit profile"
      description="Your name appears on invoices and in comments."
      icon={<Pencil />}
      footer={
        <>
          <DialogClose asChild>
            <Button variant="soft">Cancel</Button>
          </DialogClose>
          <Button tone="brand" loading={saving} loadingText="Saving" onClick={save}>
            Save
          </Button>
        </>
      }
    >
      <Input
        label="Full name"
        name="name"
        autoComplete="name"
        value={name}
        onValueChange={setName}
        error={error}
      />
    </Dialog>
  );
}
```

There is no media query in that file. Below 768px the same Dialog opens as a bottom sheet you can drag down to close, with the actions stacked full width at 48px and the primary on top; our [shadcn responsive dialog](https://wingo-ui.com/blog/shadcn-responsive-dialog-on-mobile) tutorial explains how that switch avoids a hydration flash. The demo below is a listing card for a city bike in Brooklyn at $420, and its Edit button opens the same kind of dialog.

> Live demo (Dialog): Click Edit, change the price and press Save: the button shows its loading state, then the dialog closes; on a phone the same dialog opens as a bottom sheet. Try it at [Dialog](https://wingo-ui.com/components/dialog) and install it with `npx wingo-ui@latest add dialog`.

## How do I keep the installed components up to date?

Ask Claude for a report, then decide. The two CLIs treat edited files differently, which is why the rules file sends each folder to its own tool.

For shadcn/ui files, `npx shadcn@latest add button --diff` shows your file against the registry, and `--overwrite` replaces it, edits included. The skill tells Claude to merge by hand when a file has local changes. Our guide to [updating shadcn components without losing edits](https://wingo-ui.com/blog/update-shadcn-components-without-losing-edits) has a git script that runs a real 3-way merge instead.

For Wingo UI files, the CLI recorded each item's version and kept a pristine copy at install, so it merges for you:

```bash
npx wingo-ui@latest outdated        # installed items vs the latest release
npx wingo-ui@latest diff dialog     # your file vs upstream
npx wingo-ui@latest update dialog   # 3-way merge that keeps your edits
```

With our MCP server connected, `check_updates` returns the same list with release notes, so prompt 4 works inside the session.

## Where should I start?

On shadcn/ui alone: add the MCP server, install the skill, write the three rules lines, and name `@shadcn` in every prompt. If you want a second registry built for phones, the Dialog, Button and Input from this post are free; `npx wingo-ui@latest agents install claude` adds our MCP server and skill next to shadcn's, and the [MCP server docs](https://wingo-ui.com/docs/mcp) cover the setup. The Login Page block is part of Wingo UI Pro, on the [pricing page](https://wingo-ui.com/pricing). More guides like this one live 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 button every screen starts with: five variants, six tones, three sizes plus icon sizes, and a loading state that never jumps. Install: `npx wingo-ui@latest add button`
- [Dialog](https://wingo-ui.com/components/dialog) (Free): The modal for focused tasks: a spring-scaled card on desktop that becomes a bottom sheet on phones, with nesting and a scrolling body. Install: `npx wingo-ui@latest add dialog`
- [Login Page](https://wingo-ui.com/components/login-page) (Pro): The sign-in screen of Northwind CRM: password, email link or code, Google / Apple / Microsoft, two-step codes and the whole password recovery. Install: `npx wingo-ui@latest add login-page`
- [Input](https://wingo-ui.com/components/input) (Free): The single-line text field: icons and addons inside the box, a clear button, a loading spinner, a counter and floating labels. Install: `npx wingo-ui@latest add input`

## FAQ

### How do I install the shadcn MCP server in Claude Code?

Run npx shadcn@latest mcp init --client claude in the project root. It writes a shadcn entry to .mcp.json and installs shadcn as a dev dependency; restart Claude Code and run /mcp to check that the server connected.

### What is the difference between the shadcn skill and the shadcn MCP server?

The MCP server gives Claude tools to list, search and view registry items and to get their add commands. The skill adds no tools: it injects your project config from shadcn info --json and the shadcn/ui composition rules into the session when Claude works on UI.

### Does the shadcn MCP server install components by itself?

No. As of shadcn 4.21.4 its seven tools are read-only: it returns the npx shadcn@latest add command and Claude runs it in the terminal. That Bash call normally asks for your approval, but during a turn where the shadcn skill is active, the skill's allowed-tools line lets it run without asking.

### Why does a shadcn MCP search fail with MISSING_ENV_VARS?

A registry in components.json uses an environment variable in its headers that is not set. The shadcn CLI then fails the whole call, other registries included. Export the variable or put it in .env.local, or use the plain URL form for public registries.

### Can I use shadcn/ui and another registry in the same project?

Yes, but many shadcn-style libraries install into components/ui with the same file names, starting with button.tsx. Give the second library its own folder (Wingo UI reads it from aliases.ui in wingo-ui.json) so neither CLI has to overwrite the other's files.

---

Source: https://wingo-ui.com/blog/claude-code-shadcn-mcp-setup
