MCP server that scans React/Vue projects and exposes component library metadata to AI coding agents
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:
- Duplicate components — creates
<UserCard>when<UserProfile>already exists. - Wrong prop APIs — passes
labelwhen the component expectstitle, or misses a required prop. - 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
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:
{
"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:
{
"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:
{
"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.
{ "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.
{ "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.
{
"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.
{ "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.
{
"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.
{ "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.
{ "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), legacypropTypes, JSDoc, and cross-file type expansion via the TypeScript compiler API. - Vue parsing handles
<script setup>typed + runtimedefineProps/defineEmits, Options API,defineComponent, named + scoped slots. - Import graph includes page/route directories as consumers (so
usage_countreflects imports fromsrc/pages/**), follows barrel re-exports to source files, and resolves tsconfigpathsaliases. - Performance — 200-component project scans in ~150ms; 2-file incremental scans in ~30ms.
Development
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.jsonlives. The server scansprocess.cwd()by default. - If the project root is somewhere else, set
PROJECT_ROOTin the MCP config'senvblock 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_DIRSexplicitly (e.g.,"lib/ui,packages/core/components").
Framework is wrong.
- Set
FRAMEWORK=reactorFRAMEWORK=vueexplicitly. Auto-detection readspackage.jsondeps (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=debugfor resolver errors — usually a missingtsconfig.jsonpath 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 toCOMPONENT_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 viasrc/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.