# Cursor shadcn Setup: MCP Server, .mdc Rules and Registries

> Cursor shadcn setup, tested on shadcn 4.22.0: the MCP server in .cursor/mcp.json, a second registry, an .mdc rule that always loads, and prompts that work.

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

## TL;DR

Run npx shadcn@latest mcp init --client cursor to write the shadcn server into .cursor/mcp.json, turn it on in Cursor, and list every extra registry in components.json. Then add a short .mdc project rule (or an AGENTS.md block) that says which registry owns which folder, which CLI installs it and which tokens to use. The MCP server finds components, the rule decides how Agent uses them, and the CLI writes the files.

Cursor's Agent writes shadcn/ui code from memory unless you give it something better. It will reach for the Button API it learned in training, miss the registry you added to `components.json` last week, and paste `bg-blue-500` where your theme has a token. A Cursor shadcn setup that holds up has four parts: the shadcn MCP server so Agent can search real registries, a project rule that says how to use what it finds, the registry list in `components.json`, and a CLI that writes the files. This tutorial sets up all four, adds a second registry next to shadcn/ui, and ends with prompts and a finished admin screen built on a [Data Table](https://wingo-ui.com/components/data-table), a [Dialog](https://wingo-ui.com/components/dialog) and a [Button](https://wingo-ui.com/components/button).

We ran every command below on October 16, 2026, with shadcn 4.22.0, the `skills` CLI 1.7.2 and our own registry, Wingo UI 1.9.0 (CLI 1.0.3). Statements about Cursor come from its [MCP docs](https://cursor.com/docs/context/mcp), [rules docs](https://cursor.com/docs/context/rules) and [skills docs](https://cursor.com/docs/context/skills) as of the same date. Whether Agent runs Composer or another model does not change any of it. For the wider picture of agents and component libraries, start with our [component library MCP guide](https://wingo-ui.com/blog/component-library-mcp-guide).

## What does a Cursor shadcn setup need?

One server, one rule file, one registry list and one CLI per registry:

| Piece | Job | How you add it |
| --- | --- | --- |
| shadcn MCP server | Searches every registry in `components.json`, returns add commands | `npx shadcn@latest mcp init --client cursor` |
| Project rule | Which registry owns which folder, tokens, what never to do | `.cursor/rules/<name>.mdc` or `AGENTS.md` |
| `components.json` | The registries to search, with auth headers | Edit by hand |
| The CLI | Writes files and installs npm packages | `npx shadcn@latest add`, or a library's own CLI |

A library's own MCP server is optional. It earns its place when the library publishes props and design rules, which the shadcn server does not return.

## How do I add the shadcn MCP server to Cursor?

Run one command in the project root:

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

It printed `Configuration saved to .cursor/mcp.json.`, added `shadcn` to `devDependencies`, and wrote this file:

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

That is the project scope of the Cursor MCP setup: commit `.cursor/mcp.json` and the team gets the server. The same format in `~/.cursor/mcp.json` serves every project on your machine. A new server still needs its toggle turned on in Cursor before Agent sees it; as of October 2026 Cursor's docs put the toggles under Customize in the sidebar. Once it connects, its tools appear under Available Tools, and Agent calls them when a prompt needs them.

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

Seven, all read-only. This is what the server listed once the Cursor shadcn MCP connection was up:

- `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 a list of items.
- `get_audit_checklist`: a short checklist to run after generating code.

Three things follow. The server never writes a file: every install is a terminal command that follows your run mode, and by default Cursor asks before it uses an MCP tool, so you can read each call's arguments first. The server sends no instructions, so nothing tells Agent to search before it writes UI; that is the rule's job. And `view_items_in_registries` returned no props for `@shadcn/button` or our dialog, so Agent learns an API from the installed file or the library's docs.

## How do I add a second registry?

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

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

A private registry takes an object with the URL and 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 expands `${VAR}` from your environment or from `.env.local` (then `.env.development.local`, `.env.development` and `.env`), so the key never lands in `components.json`.

With the plain URL in place, one search covers both registries. This is the real answer to `search_items_in_registries` for "data table" across `@shadcn` and `@wingo-ui` with a limit of 5, trimmed to the first three results:

```text
Found 182 items matching "data table" in registries @shadcn, @wingo-ui:

Showing items 1-5 of 182:

- data-table (registry:ui) - The admin table on TanStack Table v9: search, filters, sorting, bulk actions, pinning, resizing, virtualization and cards on phones. [@wingo-ui]
  Add command: `npx shadcn@latest add @wingo-ui/data-table`
- data-table-demo (registry:example) [@shadcn]
  Add command: `npx shadcn@latest add @shadcn/data-table-demo`
- dashboard-01 (registry:block) - A dashboard with sidebar, charts and data table. [@shadcn]
  Add command: `npx shadcn@latest add @shadcn/dashboard-01`
```

182 matches is fuzzy matching at work, so ask for a small limit. Paid items fail loudly: without a key, `view_items_in_registries` for our Data Table returned a 402 ("Data Table is part of Wingo UI Pro") and nothing for `@shadcn/button` in the same call. Ask about paid and free items separately.

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

Something breaks either way. Many shadcn-style libraries install into `components/ui` with the same file names and different APIs. Our Button's variants are `solid`, `soft`, `outline`, `ghost` and `link`, with destructive as `tone="danger"`: replacing the shadcn button breaks every `variant="destructive"` in the app, and keeping it breaks our dialog, which passes props the shadcn button lacks.

Give the second library its own folder. Our CLI reads its paths from `wingo-ui.json`, so after `npx wingo-ui@latest init` set `"ui": "@/components/wingo"` under `aliases`, and `add` writes there and rewrites the imports to match. Install our items with our CLI rather than the shadcn one: `init` merges the design tokens into `app/globals.css`, and the shadcn-format JSON at `/r/<slug>.json` carries none, so `shadow-modal` or `z-modal` would resolve to nothing. Our [shadcn registry guide](https://wingo-ui.com/blog/shadcn-registry-third-party-components) covers what else to check before you add a third-party registry.

## Does the shadcn skill work in Cursor?

It installs and Cursor finds it, with two caveats. The command:

```bash
npx skills add shadcn/ui --agent cursor
```

It copied two skills, `shadcn` and `migrate-radix-to-base`, into `.agents/skills/` and recorded them in `skills-lock.json`. Cursor discovers skills in `.agents/skills/` and `.cursor/skills/`, and for compatibility in `.claude/skills/` and `.codex/skills/` too. If the repository already has the shadcn skill for Claude Code, Cursor picks that copy up as well, so install it once.

Both caveats come from fields written for Claude Code. The `allowed-tools` line lets Claude Code run the shadcn CLI (through `npx`, `pnpm dlx` or `bunx`) without asking; Cursor's skills docs list no such field, so those commands follow your run mode. And the skill loads your project context with `npx shadcn@latest info --json` through Claude Code's command syntax, which Cursor's docs do not describe, so expect Agent to run that command itself. The skill's rules (name the registry, read the shadcn docs first, build forms from `FieldGroup`) fit shadcn/ui files and not other registries, so your rule has to draw that line.

## What should the Cursor rules say?

Who owns which folder, which tool to search with, which CLI installs, which tokens to use, and where shadcn/ui patterns stop. Cursor rules for React work best as a few lines Agent can check its output against, because an always-applied rule spends context in every chat. Project rules live in `.cursor/rules` and must use the `.mdc` extension; a plain `.md` file there is ignored. The frontmatter decides when a rule loads: `alwaysApply: true` for every chat, `globs` when a matching file is in context, a `description` alone when Agent judges it relevant, and none of them only when you @-mention it. The old single `.cursorrules` file no longer appears in Cursor's docs, so move anything you still keep there.

For a project with two registries, these are the Cursor shadcn rules we would commit as `.cursor/rules/ui-registries.mdc`:

```md
---
description: Which component registry owns which folder, how to install components, and which tokens to use
alwaysApply: true
---

- Two registries. `components/ui` is shadcn/ui (`@shadcn` in components.json). `components/wingo` is Wingo UI (wingo-ui.json); import it from `@/components/wingo/<name>`.
- Before writing UI, search: the `shadcn` MCP tools for @shadcn, 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 props of the file you import before using it. shadcn/ui patterns (`variant="default"`, `FieldGroup`, a hand-built spinner) apply to `components/ui` only.
- Colors come from tokens: `bg-background`, `bg-card`, `text-muted-foreground`, `border-border`. No hex values, no `bg-blue-500`.
- Never pass `--overwrite`. Show me the diff before updating a component.
```

Six bullets, each one checkable. Cursor suggests keeping a rule under 500 lines and adding rules "only when you notice Agent making the same mistake repeatedly." Rules reach Agent only, not Cursor Tab, and User Rules skip Inline Edit. If Claude Code or Codex work in the same repository, put these bullets in `AGENTS.md` instead, which Cursor reads too; our [AGENTS.md vs CLAUDE.md comparison](https://wingo-ui.com/blog/agents-md-vs-claude-md-react) explains which file each agent loads.

## How do I connect a library's MCP server next to shadcn's?

Add a second entry to the same `.cursor/mcp.json`. For our library, one command writes both the server entry and a library rule:

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

In our test project it created `.cursor/rules/wingo-ui.mdc`, scoped with `globs: ["**/*.tsx", "**/*.jsx", "**/*.css"]`, and kept the shadcn entry while adding ours:

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

That is the local proxy, which uses the key saved by `npx wingo-ui@latest login`. With `WINGO_UI_API_KEY` exported, the command writes the remote server instead, with Cursor's own variable syntax so the key stays out of the file: `"headers": { "Authorization": "Bearer ${env:WINGO_UI_API_KEY}" }`. Our server adds what the shadcn one lacks: props with defaults (`get_component`), design rules and tokens (`get_design_rules`, `get_theme_tokens`) and updates (`check_updates`). Turn it on with its toggle, like shadcn's.

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

The ones that name the registry, ask for a search before an install, and say how to check the result:

1. **Search first.** "Search @shadcn and @wingo-ui for an admin data table with bulk actions and cards on phones. List each candidate with its registry and add command. Install nothing yet."
2. **Build on the real API.** "Install the Wingo UI data-table and dialog with the Wingo CLI, read their props with get_component, then build app/clients/clients-table.tsx: search, a status badge, a balance total, row actions, and a confirm dialog before delete."
3. **Check the tokens.** "List every color class in the files you touched and replace anything that is not a semantic token."
4. **End with a check.** "Run get_audit_checklist, then type-check and lint the files you changed."

Here is the table that prompt 2 starts from. Type in the search, sort by Balance, then select two rows: the toolbar turns into a bulk bar.

> Live demo (Data Table): Search for a client, sort by Balance, then select two rows and watch the toolbar turn into the bulk bar; in a narrow frame every row becomes a card. Try it at [Data Table](https://wingo-ui.com/components/data-table) and install it with `npx wingo-ui@latest add data-table`.

A good run of prompt 2 ends with a file like this, on the real APIs of the Data Table, the Dialog and the Button. We type-checked it against release 1.9.0:

```tsx
// app/clients/clients-table.tsx
"use client";

import { useState } from "react";
import { Pencil, Plus, Trash2 } from "lucide-react";
// next to shadcn/ui these imports read @/components/wingo/...
import { Button } from "@/components/ui/button";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";
import { Dialog, DialogClose } from "@/components/ui/dialog";

export type Client = {
  id: string;
  name: string;
  city: string;
  status: "Active" | "Prospect" | "Overdue";
  balance: number;
  lastInvoice: string; // ISO date
};

const COLUMNS: DataTableColumn<Client>[] = [
  { id: "name", header: "Client", filter: "text", mobile: "title" },
  { id: "city", header: "City", filter: "multi-select" },
  {
    id: "status",
    header: "Status",
    type: "badge",
    badge: {
      Active: { tone: "success" },
      Prospect: { tone: "info" },
      Overdue: { tone: "danger" },
    },
    filter: "multi-select",
  },
  { id: "balance", header: "Balance", type: "currency", footer: "sum", filter: "number-range" },
  { id: "lastInvoice", header: "Last invoice", type: "relative" },
];

type ClientsTableProps = {
  clients: Client[];
  onCreate: () => void;
  onEdit: (client: Client) => void;
  onDelete: (ids: string[]) => Promise<void>;
};

export function ClientsTable({ clients, onCreate, onEdit, onDelete }: ClientsTableProps) {
  const [open, setOpen] = useState(false);
  const [pending, setPending] = useState<Client[]>([]);
  const [deleting, setDeleting] = useState(false);

  function askDelete(rows: Client[]) {
    setPending(rows);
    setOpen(true);
  }

  async function confirmDelete() {
    setDeleting(true);
    try {
      await onDelete(pending.map((client) => client.id));
      setOpen(false);
    } finally {
      setDeleting(false);
    }
  }

  const title = pending.length === 1 ? `Delete ${pending[0]?.name}?` : `Delete ${pending.length} clients?`;

  return (
    <>
      <DataTable<Client>
        data={clients}
        columns={COLUMNS}
        caption="Clients"
        locale="en-US"
        searchPlaceholder="Search clients"
        selectionMode="multiple"
        toolbarActions={[{ id: "new", label: "New client", icon: <Plus />, onSelect: onCreate, primary: true }]}
        bulkActions={(rows) => [
          { id: "delete", label: "Delete", icon: <Trash2 />, tone: "danger", onSelect: () => askDelete(rows) },
        ]}
        rowActions={(row) => [
          { id: "edit", label: "Edit", icon: <Pencil />, onSelect: () => onEdit(row) },
          { id: "delete", label: "Delete", icon: <Trash2 />, tone: "danger", onSelect: () => askDelete([row]) },
        ]}
      />
      <Dialog
        open={open}
        onOpenChange={(next) => !deleting && setOpen(next)}
        dismissible={!deleting}
        title={title}
        description="Their invoices stay in your records. This cannot be undone."
        icon={<Trash2 />}
        tone="danger"
        footer={
          <>
            <DialogClose asChild>
              <Button variant="soft" disabled={deleting}>
                Cancel
              </Button>
            </DialogClose>
            <Button tone="danger" loading={deleting} loadingText="Deleting" onClick={confirmDelete}>
              Delete
            </Button>
          </>
        }
      />
    </>
  );
}
```

Colors come from tones, never classes, so dark mode needs no extra code. The `pending` list outlives the dialog, so the title does not flip to "Delete 0 clients?" while it animates out. The delete Button uses `loading` instead of a hand-built spinner. While the request runs, `dismissible={!deleting}` stops Escape, outside clicks and the sheet drag from closing it, and the guarded `onOpenChange` keeps the close button from dropping the dialog mid-request. There is no media query: below 768px the table renders cards and the dialog opens as a bottom sheet. Server mode and the rest of the table's props are in our [TanStack Table v9 data table](https://wingo-ui.com/blog/tanstack-table-v9-data-table) guide.

> Live demo (Dialog): Click Edit to open the dialog and press Save to see the loading state; below 768px the same Dialog opens as a bottom sheet you can drag down to close. Try it at [Dialog](https://wingo-ui.com/components/dialog) and install it with `npx wingo-ui@latest add dialog`.

## Why does Cursor ignore the MCP server or the rules?

Usually one of five things:

- **The server is off.** A server listed in `.cursor/mcp.json` stays out of chat until its toggle under Customize is on, and its tools are missing from Available Tools until then.
- **The server failed to start.** Open the Output panel (Cmd+Shift+U on a Mac) and pick "MCP Logs". A stdio server needs Node and network access on first run for `npx`.
- **A registry variable is missing.** When a `components.json` registry uses `${VAR}` in its headers and the variable is not set, the shadcn CLI fails the whole call with `MISSING_ENV_VARS`, other registries included. Export it or put it in `.env.local`.
- **The rule never loads.** Check the `.mdc` extension, then the frontmatter: a `globs`-only rule waits for a matching file in context. Use `alwaysApply: true` for registry and token rules.
- **The prompt names no registry.** "Add a table" leaves Agent to its memory. Name the registry and say "search first".

## Where should I start?

On shadcn/ui alone: run `mcp init --client cursor`, turn the server on, and commit a three-line rule that says search first, install with the CLI, tokens only. If you want a second registry built for phones, the Button and Dialog from this post are free, and `npx wingo-ui@latest agents install cursor` adds our server and rule next to shadcn's; the [MCP server docs](https://wingo-ui.com/docs/mcp) and the [coding agents docs](https://wingo-ui.com/docs/agents) cover the rest. The Data Table is part of Wingo UI Pro, on the [pricing page](https://wingo-ui.com/pricing). Running Claude Code in the same repository? The [Claude Code shadcn setup](https://wingo-ui.com/blog/claude-code-shadcn-mcp-setup) is the companion to this post, and more guides 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 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`
- [Data Table](https://wingo-ui.com/components/data-table) (Pro): A React data table on TanStack Table: search, filters, sorting, bulk actions, pinning, resizing, virtualization and cards on phones. Install: `npx wingo-ui@latest add data-table`

## FAQ

### How do I add the shadcn MCP server to Cursor?

Run npx shadcn@latest mcp init --client cursor in the project root. It writes a shadcn entry to .cursor/mcp.json and installs shadcn as a dev dependency; then turn the server on with its toggle in Cursor and check that its seven tools are listed.

### Where do Cursor rules for shadcn go?

In .cursor/rules, as files with the .mdc extension; Cursor ignores a plain .md file there. Cursor also reads AGENTS.md in the project root, which is the better home for rules that Claude Code or Codex should follow too.

### Does the shadcn MCP server install components in Cursor?

No. Its seven tools are read-only: it returns an npx shadcn@latest add command and Agent runs it in the terminal, where Cursor's run mode decides whether you approve it first.

### Is .cursorrules still supported?

As of October 2026 Cursor's rules docs no longer mention .cursorrules at all and describe project rules in .cursor/rules plus AGENTS.md. Move old .cursorrules content into one of those.

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

Yes. Add the second registry to components.json so one search covers both, and give the second library its own folder, because many shadcn-style libraries ship a components/ui/button.tsx with a different API.

---

Source: https://wingo-ui.com/blog/cursor-shadcn-mcp-rules
