Read OSS

AI-First Component Discovery: The MCP Server and Programmatic Registry API

Intermediate

Prerequisites

  • Article 1: architecture-overview
  • Article 2: registry-system-and-dependency-resolution
  • Basic understanding of the Model Context Protocol (MCP) concept

AI-First Component Discovery: The MCP Server and Programmatic Registry API

The shadcn/ui CLI was designed from the start to be more than a human-facing tool. As we saw in Part 1, the package exposes 7 sub-path exports serving three audiences. This final article examines the most forward-looking of those surfaces: the MCP server that lets AI coding assistants discover, inspect, and install components, plus the programmatic registry API that library authors can import directly.

MCP Server Architecture

The MCP server at packages/shadcn/src/mcp/index.ts is built on @modelcontextprotocol/sdk. It creates a Server instance with tools capability and registers 7 tools:

graph TD
    MCP["MCP Server (shadcn)"]
    MCP --> T1["get_project_registries"]
    MCP --> T2["list_items_in_registries"]
    MCP --> T3["search_items_in_registries"]
    MCP --> T4["view_items_in_registries"]
    MCP --> T5["get_item_examples_from_registries"]
    MCP --> T6["get_add_command_for_items"]
    MCP --> T7["get_audit_checklist"]

Each tool has a Zod input schema that's converted to JSON Schema via zod-to-json-schema for the MCP protocol. The server runs as a stdio process — when you execute shadcn mcp, it connects via StdioServerTransport and waits for tool invocations.

The error handling in the CallToolRequestSchema handler (lines 413-462) shows the bridge between the registry error system from Article 2 and MCP responses. RegistryError instances are formatted with their code, message, suggestion, and context — giving AI assistants enough information to diagnose problems and suggest fixes.

The 7 MCP Tools: Discovery → Inspection → Installation

The tools form a natural workflow progression:

1. get_project_registries — Reads components.json and returns configured registry names. This is always the first call an AI makes to understand what's available.

2. list_items_in_registries — Lists all items from specified registries with pagination. Takes registries: string[], limit, and offset.

3. search_items_in_registries — Fuzzy searches across registries. The implementation at lines 197-240 delegates to searchRegistries, which uses the fuzzysort library internally.

4. view_items_in_registries — Fetches full item details including file content. Takes items with registry prefix (e.g., ["@shadcn/button"]).

5. get_item_examples_from_registries — Searches for demo/example items and returns their complete code. The tool is designed for patterns like accordion-demo, button example, example-hero.

6. get_add_command_for_items — Returns the CLI command to install items (e.g., npx shadcn@latest add @shadcn/button).

7. get_audit_checklist — Returns a post-installation checklist covering common issues: import correctness, next.config.js image patterns, dependency installation, linting, TypeScript errors.

flowchart LR
    A["1. get_project_registries"] --> B["2. list/search items"]
    B --> C["3. view item details"]
    C --> D["4. get examples"]
    D --> E["5. get add command"]
    E --> F["6. User runs command"]
    F --> G["7. audit checklist"]

Tip: The MCP tools intentionally don't execute shadcn add directly — they return the command string for the user to run. This is a safety design: AI assistants suggest actions, humans approve them.

Client Configuration per IDE

The mcp command includes an init subcommand that generates configuration for five AI coding environments. Each has a different config format:

Client Config Path Format
Claude Code .mcp.json { mcpServers: { shadcn: { command, args } } }
Cursor .cursor/mcp.json { mcpServers: { shadcn: { command, args } } }
VS Code .vscode/mcp.json { servers: { shadcn: { command, args } } }
Codex .codex/config.toml TOML format ([mcp_servers.shadcn])
OpenCode opencode.json { mcp: { shadcn: { type, command, enabled } } }

The CLIENTS array at lines 22-86 defines each client's config path and structure. The runMcpInit function reads existing config, deep-merges the shadcn server config, and writes it back. Existing MCP servers in the config file are preserved.

Codex gets special treatment because its config lives in ~/.codex/config.toml (a global location), not in the project directory. The command prints manual instructions instead of writing the file automatically.

The Programmatic Registry API

The shadcn/registry export at packages/shadcn/src/registry/index.ts re-exports the public API surface:

export { getRegistries, getRegistryItems, resolveRegistryItems, 
         getRegistry, getRegistriesIndex } from "./api"
export { searchRegistries } from "./search"
export { RegistryError, RegistryNotFoundError, /* ... */ } from "./errors"

This is the same API the MCP server uses internally. Library authors can import it to build custom tooling:

import { searchRegistries, getRegistryItems } from "shadcn/registry"

const results = await searchRegistries(["@shadcn"], {
  query: "button",
  limit: 10,
})

The api.ts module composes the builder, fetcher, and resolver internally. getRegistryItems handles both namespaced (@acme/button) and URL items, sets up the registry context for auth headers, and validates responses against the Zod schema.

Search Subsystem

The searchRegistries function implements search across multiple registries:

  1. For each registry, fetch the full registry data via getRegistry
  2. Map items to a searchable format with addCommandArgument (e.g., @shadcn/button)
  3. If a query is provided, run fuzzy matching via fuzzysort on name and description fields
  4. Apply pagination with offset and limit
  5. Validate the result against searchResultsSchema
flowchart TD
    A["searchRegistries(['@shadcn', '@acme'], {query: 'button'})"]
    A --> B["Fetch @shadcn registry"]
    A --> C["Fetch @acme registry"]
    B --> D["Map to searchable items"]
    C --> E["Map to searchable items"]
    D --> F["Concatenate all items"]
    E --> F
    F --> G["fuzzysort.go(query, items)"]
    G --> H["Apply pagination"]
    H --> I["Return SearchResults"]

The fuzzysort library provides typo-tolerant matching with a configurable threshold (defaulting to -10000, which is very permissive). Results include the addCommandArgument field, which is the exact string to pass to shadcn add.

Triple-Surface Design: CLI, Library, and AI

Looking at the architecture as a whole, shadcn/ui is intentionally designed as a triple-surface system:

graph TD
    subgraph "Shared Internals"
        Parser["parser.ts"]
        Builder["builder.ts"]
        Fetcher["fetcher.ts"]
        Resolver["resolver.ts"]
        Schema["schema.ts"]
        Context["context.ts"]
        Search["search.ts"]
        API["api.ts"]
    end

    subgraph "Surface 1: CLI"
        Init["init command"]
        Add["add command"]
        Build["build command"]
    end

    subgraph "Surface 2: Library"
        RegExport["shadcn/registry"]
        SchemaExport["shadcn/schema"]
    end

    subgraph "Surface 3: AI"
        MCP["shadcn/mcp"]
        Tools["7 MCP tools"]
    end

    Init --> API
    Add --> API
    Build --> Schema
    RegExport --> API
    SchemaExport --> Schema
    MCP --> API
    MCP --> Search
    Tools --> MCP

    API --> Parser
    API --> Builder
    API --> Fetcher
    API --> Resolver
    API --> Context
    Search --> API

The key architectural principle is that all three surfaces share the same internal modules. When the resolver gains a new feature (like the topological sort from Article 2), it's immediately available to CLI users, programmatic consumers, and AI assistants. There's no adapter layer or separate implementation — just different entry points into the same code.

The package.json sub-path exports enforce clean boundaries. You can import shadcn/registry without pulling in the CLI's Commander dependency. You can import shadcn/schema for just the Zod schemas without any runtime code. The tsup.config.ts tree-shaking ensures each entry point only bundles what it needs.

This isn't accidental. The index.ts CLI entry point's final line — export * from "./registry/api" — was a deliberate choice to make the CLI module also a programmatic library. It's the smallest possible API surface that enables the widest range of consumption patterns.

Tip: If you're building tooling around shadcn/ui (VS Code extensions, CI checks, documentation generators), import from shadcn/registry rather than the root export. You'll get a smaller bundle and avoid pulling in CLI-specific dependencies.

Series Conclusion

Over these six articles, we've traced the complete architecture of shadcn/ui:

  1. Architecture: A monorepo with a multi-surface CLI package and a registry protocol
  2. Registry Protocol: Namespace parsing, URL construction, HTTP fetching, and topological sort
  3. Transformers: AST-level code rewriting via ts-morph and PostCSS style maps
  4. Build Pipeline: Cartesian product of bases × styles producing static JSON
  5. Initialization: Framework detection, templates, presets, and monorepo routing
  6. AI Integration: MCP server, programmatic API, and triple-surface design

The architectural insight that ties it all together is the cn-* abstraction layer. By decoupling component logic from visual styling via abstract CSS classes, shadcn/ui achieves something rare: a single component implementation that works across multiple headless libraries, multiple visual styles, multiple icon libraries, and multiple project configurations — all resolved at install time rather than runtime. The code you get in your project has no abstraction overhead. It's just components, styled exactly the way you chose.