Component Library MCP
by vasyl-bilous
README.md
# Component Library MCP
An MCP (Model Context Protocol) server that scans React and Vue projects, extracts component metadata (props, slots, events, imports, usage), and exposes it to AI coding agents via structured tools.
**What it solves:** When an AI coding agent writes UI code without context, it produces three recurring problems:
1. **Duplicate components** — creates `<UserCard>` when `<UserProfile>` already exists.
2. **Wrong prop APIs** — passes `label` when the component expects `title`, or misses a required prop.
3. **Inconsistent patterns** — builds a one-off dialog instead of using the project's `<BaseDialog>`.
This MCP gives the agent full visibility into your component library **before** it writes any UI code.
---
## Requirements
- Node.js ≥ 20
- pnpm (other package managers also work — install deps however you like, the server is a plain Node script at runtime)
## Install & build
```bash
git clone <this-repo>
cd component-library-mcp
pnpm install
pnpm build
```
`pnpm build` emits a runnable server at `dist/index.js`.
## Configure in Claude Code
The server scans whatever directory it is launched from (`process.cwd()`). Pick one of the two setups below — no `PROJECT_ROOT` needed.
### Option A — Per-project `.mcp.json` (recommended)
Drop a `.mcp.json` file at the root of every React/Vue project you want to use the MCP with:
```json
{
"mcpServers": {
"component-library": {
"command": "node",
"args": ["/absolute/path/to/component-library-mcp/dist/index.js"]
}
}
}
```
Claude Code auto-loads `.mcp.json` from the open workspace. Each project can have its own `env` overrides (see below) without polluting other projects.
### Option B — Global `~/.claude.json`
If you usually work in one project at a time and always launch Claude Code from that project's directory:
```json
{
"mcpServers": {
"component-library": {
"command": "node",
"args": ["/absolute/path/to/component-library-mcp/dist/index.js"]
}
}
}
```
With no env vars set, the server scans `process.cwd()` — which is the directory Claude Code is opened in. Switch projects by opening a different folder in your editor / re-launching Claude Code from a different directory.
Restart Claude Code after changing either config. The server runs on stdio — there's no port, no daemon.
## Environment variables (optional)
All env vars are optional. Set them under `env: { ... }` in the MCP config only when you need to override defaults.
| Variable | Default | Description |
|---|---|---|
| `PROJECT_ROOT` | `process.cwd()` | Absolute path to scan. Usually you don't need to set this — the server uses the directory it was launched from. Only set it when that isn't what you want. |
| `COMPONENT_DIRS` | `auto` | Comma-separated dirs to scan. `auto` probes `src/components`, `src/ui`, `src/shared`, `components`, `lib/components`, `app/components` and falls back to `src/` or the project root. |
| `FRAMEWORK` | `auto` | `react` \| `vue` \| `auto`. Auto detects from `package.json` deps. |
| `EXCLUDE_PATTERNS` | `node_modules,dist,.next,.nuxt` | Comma-separated dir names or globs. Bare names become `**/<name>/**`. |
| `MAX_DEPTH` | `10` | Max recursion depth for `fast-glob`. |
| `INCLUDE_STORIES` | `true` | Parse `.stories.(ts\|tsx\|js\|jsx)` and attach story examples to components. |
| `LOG_LEVEL` | `silent` | `silent` \| `info` \| `debug`. All logs go to stderr. |
Example `.mcp.json` with overrides:
```json
{
"mcpServers": {
"component-library": {
"command": "node",
"args": ["/absolute/path/to/component-library-mcp/dist/index.js"],
"env": {
"COMPONENT_DIRS": "src/components,src/ui",
"FRAMEWORK": "react",
"EXCLUDE_PATTERNS": "node_modules,dist,__tests__,stories",
"INCLUDE_STORIES": "false",
"LOG_LEVEL": "info"
}
}
}
}
```
## Available tools
All 7 tools accept Zod-validated JSON input and return both `content` (text) and `structuredContent` (same data as structured output).
### 1. `list_components`
Summary of every component in the project. Agents typically call this first.
```jsonc
{ "framework": "react", "min_usage": 1, "include_props": false }
```
Returns an alphabetically sorted array: `{ name, file_path, framework, prop_count, usage_count, has_stories, description?, tags? }`.
### 2. `get_component_details`
Full metadata for one component — props, slots, events, description, story examples.
```jsonc
{ "name": "Button" }
// or
{ "file_path": "src/components/Button.tsx" }
```
Fuzzy fallback: if `name` has no exact match, suggestions within Levenshtein distance ≤ 3 are returned.
### 3. `search_components`
Natural-language + structured search.
```jsonc
{
"query": "dialog modal",
"prop_type": "boolean",
"has_slot": "footer",
"limit": 10
}
```
Returns ranked `{ name, score, reasons[] }` hits. Weights: name 1.0, description 0.5, tags 0.6, prop names 0.35, path 0.2.
### 4. `get_import_graph`
Who imports what.
```jsonc
{ "component_name": "Button", "direction": "both", "depth": 2 }
```
Returns `dependencies[]` (what this component imports) and `dependents[]` (who imports it, with `is_page: true` for pages/routes). Barrel files are followed through but not counted as dependents.
### 5. `check_duplicate` — **highest-value tool**
Call this *before* creating a new component.
```jsonc
{
"proposed_name": "UserCard",
"proposed_props": ["user", "size", "avatarUrl"],
"purpose": "display user profile card"
}
```
Returns `{ has_duplicates, existing_matches[], recommendation }` where recommendation is `use_existing` | `extend_existing` | `create_new`. Score combines name similarity (0.45), prop overlap (0.35), and purpose/description overlap (0.2), with a short-circuit to `use_existing` when proposed props are a full subset of an existing component.
### 6. `get_component_usage_example`
Real usage snippets from the codebase.
```jsonc
{ "component_name": "Button", "limit": 3 }
```
Returns up to `limit` `{ file_path, code_snippet, props_used[] }` examples (5–15 lines around the import line), plus `story_examples[]` from any matching `.stories.(t|j)sx` files.
### 7. `refresh_cache`
Force re-scan. Incremental by default.
```jsonc
{ "full_scan": true }
```
Returns `{ components_found, scan_duration_ms, new_components[], removed_components[], reparsed_files[] }`.
## How it works
- **First tool call** triggers a full scan. Subsequent calls do an `mtime`-based incremental scan — only re-parses files that changed.
- **React parsing** handles function/arrow/FC components, HOC unwrapping (`memo`, `forwardRef`, `observer`, nested), legacy `propTypes`, JSDoc, and cross-file type expansion via the TypeScript compiler API.
- **Vue parsing** handles `<script setup>` typed + runtime `defineProps` / `defineEmits`, Options API, `defineComponent`, named + scoped slots.
- **Import graph** includes page/route directories as consumers (so `usage_count` reflects imports from `src/pages/**`), follows barrel re-exports to source files, and resolves tsconfig `paths` aliases.
- **Performance** — 200-component project scans in ~150ms; 2-file incremental scans in ~30ms.
## Development
```bash
pnpm dev # tsx watch mode
pnpm test # vitest, all tests
pnpm test:perf # perf tests only
pnpm test:watch # vitest watch mode
pnpm build # tsc → dist/
```
## Troubleshooting
**The server returns `{ components: [], total: 0 }` for my project.**
- Make sure Claude Code was launched from (or has opened) the project root — where `package.json` lives. The server scans `process.cwd()` by default.
- If the project root is somewhere else, set `PROJECT_ROOT` in the MCP config's `env` block as a last resort.
- Run with `LOG_LEVEL=info`. stderr will show which dirs the scanner tried and how many files it found.
- If you have a non-standard layout, set `COMPONENT_DIRS` explicitly (e.g., `"lib/ui,packages/core/components"`).
**Framework is wrong.**
- Set `FRAMEWORK=react` or `FRAMEWORK=vue` explicitly. Auto-detection reads `package.json` deps (`react`, `next`, `vue`, `nuxt`).
**Cross-file types show up as `"Props"` instead of `"a" | "b"`.**
- The TS type resolver runs automatically. If it fails, the parser falls back to the alias name. Check `LOG_LEVEL=debug` for resolver errors — usually a missing `tsconfig.json` path alias.
**`usage_count` is 0 for a component that's clearly used.**
- Your importing files must live inside the scanned directories, or inside one of the auto-detected page dirs (`src/pages`, `src/routes`, `src/app`, `pages`, `routes`, `app`). If imports come from a non-standard dir, add it to `COMPONENT_DIRS`.
**JSON-RPC protocol errors from the client.**
- The server writes only JSON-RPC to stdout. If you see garbage there, a dependency is using `console.log` — open an issue. All of our logging goes to stderr via `src/logger.ts`.
## Project layout
```
src/
index.ts # stdio entry point
server.ts # MCP server + tool registration
config.ts # env var parsing
logger.ts # stderr-only logger
lib/
types.ts # ComponentInfo, PropInfo, etc.
schemas.ts # Zod schemas for tool inputs
scanner/
file-scanner.ts # component dir discovery
page-scanner.ts # page/route dir discovery (for graph only)
framework-detector.ts # react vs vue
import-resolver.ts # relative + tsconfig paths
import-extractor.ts # AST → import statements
import-graph.ts # dependencies / dependents / usages
parsers/
react-parser.ts # TSX + HOC unwrap
vue-sfc-parser.ts # <script setup> / Options API / template slots
vue-tsx-parser.ts # Vue TSX (thin wrapper over react parser)
story-parser.ts # .stories.tsx
jsdoc-parser.ts # @tag, @param, description
ts-type-resolver.ts # cross-file type expansion via ts.Program
shared.ts
search/
fuzzy-match.ts # Levenshtein + token overlap
text-search.ts # ranked + structured filters
duplicate-detector.ts # name + prop + purpose scoring
cache/
component-cache.ts # in-memory file_path → entry
incremental-scan.ts # mtime diff + graph rebuild + story join
tools/
list-components.ts
get-component-details.ts
search-components.ts
get-import-graph.ts
check-duplicate.ts
get-usage-example.ts
refresh-cache.ts
tests/
fixtures/ # react-project, vue-project, mixed, nonstandard
parsers/ scanner/ search/ cache/ tools/ perf/
```
## License
ISC.
TDQS
B3.4/5.0
Scored across 7 tools
Disambiguation5/5
Each tool has a distinct purpose: checking duplicates, getting details, usage examples, import graph, listing, refreshing cache, and searching. No overlap, clear boundaries.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern in snake_case (e.g., check_duplicate, list_components, refresh_cache), making them predictable and readable.
Tool Count5/5
Seven tools cover the essential operations for a component library: discovery, inspection, dependency analysis, and cache management. The scope is well-balanced, neither too sparse nor excessive.
Completeness3/5
The tool surface covers querying and analysis well (list, search, details, examples, dependencies) but lacks any creation or modification tools, which is a notable gap given the server's domain.
Maintenance
ActivityInactive
ResponsivenessNo issues