# Directory Structure
```
├── .gitignore
├── .husky
│ └── commit-msg
├── .npmignore
├── .npmrc
├── commitlint.config.mjs
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── public
│ └── logo.svg
├── README.md
├── src
│ ├── lib
│ │ ├── categories.ts
│ │ └── config.ts
│ ├── server.ts
│ └── utils
│ ├── api.ts
│ ├── formatters.ts
│ ├── index.ts
│ └── schemas.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
```
1 | tag-version-prefix="v"
2 | message="chore(release): %s"
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | .DS_Store
2 | dist/
3 | node_modules/
4 |
5 | tsconfig.tsbuildinfo
6 | .husky/_
7 |
8 | .env
9 | .env.*
10 |
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
1 | # Source files
2 | src/
3 | tsconfig.json
4 | tsconfig.tsbuildinfo
5 |
6 | # Development files
7 | .git/
8 | .gitignore
9 | .DS_Store
10 |
11 | # Node modules and lock files
12 | node_modules/
13 | pnpm-lock.yaml
14 | yarn.lock
15 | package-lock.json
16 |
17 | # Development and testing
18 | *.log
19 | coverage/
20 | .nyc_output/
21 | .env
22 | .env.local
23 | .env.*.local
24 |
25 | # IDE files
26 | .vscode/
27 | .idea/
28 | *.swp
29 | *.swo
30 |
31 | # Build artifacts (keep only what's in "files" array)
32 | # Everything else will be excluded by default since we specified "files": ["dist"]
33 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | <!-- PROJECT LOGO -->
2 | <br />
3 | <div align="center">
4 | <a href="https://github.com/stackzero-labs/ui">
5 | <img src="public/logo.svg" alt="Logo" width="80" height="80">
6 | </a>
7 |
8 | <h3 align="center">@stackzero-labs/mcp</h3>
9 |
10 | <p align="center">
11 | Official MCP (Model Context Protocol) server for stackzero-labs/ui.
12 | <br />
13 | <a href="https://ui.stackzero.co"><strong>Official Docs »</strong></a>
14 | <br />
15 | <br />
16 | <a href="https://ui.stackzero.co/get-started">Get started</a>
17 | ·
18 | <a href="https://github.com/stackzero-labs/mcp/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
19 | ·
20 | <a href="https://github.com/stackzero-labs/mcp/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
21 | </p>
22 | </div>
23 |
24 | ## Overview
25 |
26 | This package allows you to run a model context server for stackzero-labs/ui components, enabling you to use the MCP protocol with your applications.
27 |
28 | ## 1-click install in Cursor
29 |
30 | [](https://cursor.com/install-mcp?name=%40stackzero-labs%2Fmcp&config=eyJjb21tYW5kIjoibnB4IC15IEBzdGFja3plcm8tbGFicy9tY3BAbGF0ZXN0In0%3D)
31 |
32 | ## Installation
33 |
34 | ```bash
35 | npm install @stackzero-labs/mcp
36 | ```
37 |
38 | ## Usage
39 |
40 | ### As a standalone server
41 |
42 | ```bash
43 | npx @stackzero-labs/mcp
44 | ```
45 |
46 | ### In Claude Desktop
47 |
48 | Add to your Claude Desktop configuration:
49 |
50 | ```json
51 | {
52 | "mcpServers": {
53 | "@stackzero-labs/mcp": {
54 | "command": "npx",
55 | "args": ["-y", "@stackzero-labs/mcp@latest"]
56 | }
57 | }
58 | }
59 | ```
60 |
61 | ### In Cursor (manual setup)
62 |
63 | Go to Cursor settings, select `MCP`. Add to your Cursor configuration:
64 |
65 | ```json
66 | {
67 | "mcpServers": {
68 | "@stackzero-labs/mcp": {
69 | "command": "npx",
70 | "args": ["-y", "@stackzero-labs/mcp@latest"]
71 | }
72 | }
73 | }
74 | ```
75 |
76 | ### Development
77 |
78 | ```bash
79 | # Install dependencies
80 | pnpm install
81 |
82 | # Build the project
83 | pnpm build
84 |
85 | # Run in development mode
86 | pnpm dev
87 |
88 | # Inspect the MCP server
89 | pnpm inspect
90 | ```
91 |
92 | ## License
93 |
94 | See [LICENSE](LICENSE) for details.
95 |
```
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export * from "./api.js";
2 | export * from "./formatters.js";
3 | export * from "./schemas.js";
4 |
```
--------------------------------------------------------------------------------
/src/lib/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * General configuration for MCP service
3 | */
4 | export const mcpConfig = {
5 | projectName: "@stackzero-labs/ui",
6 | baseUrl: "https://ui.stackzero.co",
7 | registryUrl: "https://ui.stackzero.co/r",
8 | registryFileUrl: "https://ui.stackzero.co/registry.json",
9 | };
10 |
```
--------------------------------------------------------------------------------
/src/utils/formatters.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Formats a component name by converting it from kebab-case to PascalCase.
3 | * @param componentName
4 | * @returns
5 | */
6 | export function formatComponentName(componentName: string): string {
7 | return componentName
8 | .split("-")
9 | .map((part) => {
10 | return part.charAt(0).toUpperCase() + part.slice(1);
11 | })
12 | .join("");
13 | }
14 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "outDir": "dist",
7 | "rootDir": "src",
8 | "sourceMap": true,
9 | "declaration": true,
10 | "strict": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "skipLibCheck": true
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "dist"]
17 | }
18 |
```
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
```
1 | <svg width="217" height="216" viewBox="0 0 217 216" fill="none" xmlns="http://www.w3.org/2000/svg">
2 | <g clip-path="url(#clip0_1134_83)">
3 | <path d="M91.8543 167.855C112.307 173.221 135.092 167.808 151.24 151.659C167.389 135.511 172.803 112.726 167.437 92.2731L91.8543 167.855Z" fill="#00FF9D"/>
4 | <path d="M158.581 74.1351C156.455 71.0144 154.008 68.047 151.241 65.2791C127.387 41.426 88.714 41.426 64.8609 65.2791C41.0078 89.1322 41.0078 127.806 64.8609 151.659C67.6288 154.427 70.5962 156.873 73.7169 158.999L158.581 74.1351Z" fill="#8940FF"/>
5 | </g>
6 | <defs>
7 | <clipPath id="clip0_1134_83">
8 | <rect width="152.699" height="152.699" fill="white" transform="translate(108.051 215.949) rotate(-135)"/>
9 | </clipPath>
10 | </defs>
11 | </svg>
12 |
```
--------------------------------------------------------------------------------
/commitlint.config.mjs:
--------------------------------------------------------------------------------
```
1 | const Configuration = {
2 | extends: ["@commitlint/config-conventional"],
3 | rules: {
4 | "type-enum": [
5 | 2,
6 | "always",
7 | [
8 | "feat", // A new feature
9 | "fix", // A bug fix
10 | "docs", // Documentation only changes
11 | "style", // Changes that do not affect the meaning of the code
12 | "refactor", // A code change that neither fixes a bug nor adds a feature
13 | "perf", // A code change that improves performance
14 | "test", // Adding missing tests or correcting existing tests
15 | "chore", // Changes to the build process or auxiliary tools
16 | "ci", // Changes to CI configuration files and scripts
17 | "build", // Changes that affect the build system or external dependencies
18 | "revert", // Reverts a previous commit
19 | ],
20 | ],
21 | "type-case": [2, "always", "lower-case"],
22 | "type-empty": [2, "never"],
23 | "scope-case": [2, "always", "lower-case"],
24 | "subject-case": [
25 | 2,
26 | "never",
27 | ["sentence-case", "start-case", "pascal-case", "upper-case"],
28 | ],
29 | "subject-empty": [2, "never"],
30 | "subject-full-stop": [2, "never", "."],
31 | "header-max-length": [2, "always", 100],
32 | "body-leading-blank": [1, "always"],
33 | "body-max-line-length": [2, "always", 100],
34 | "footer-leading-blank": [1, "always"],
35 | "footer-max-line-length": [2, "always", 100],
36 | },
37 | };
38 |
39 | export default Configuration;
40 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@stackzero-labs/mcp",
3 | "version": "0.4.0",
4 | "author": "@stackzero-labs",
5 | "keywords": [
6 | "mcp",
7 | "model-context-protocol",
8 | "ai",
9 | "claude",
10 | "server"
11 | ],
12 | "license": "MIT",
13 | "description": "MCP (Model Context Protocol) server for stackzero-labs/ui",
14 | "homepage": "https://ui.stackzero.co",
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/stackzero-labs/mcp.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/stackzero-labs/mcp/issues"
21 | },
22 | "scripts": {
23 | "build": "tsup src/server.ts --format esm,cjs --dts --out-dir dist",
24 | "build:old": "tsc && shx chmod +x dist/*.js",
25 | "start": "node dist/server.js",
26 | "dev": "tsx watch src/server.ts",
27 | "inspect": "mcp-inspector node dist/server.js",
28 | "prepublishOnly": "pnpm run build",
29 | "prepare": "husky"
30 | },
31 | "type": "module",
32 | "main": "./dist/server.js",
33 | "module": "./dist/server.js",
34 | "types": "./dist/server.d.ts",
35 | "bin": {
36 | "mcp": "./dist/server.js"
37 | },
38 | "files": [
39 | "dist"
40 | ],
41 | "dependencies": {
42 | "@modelcontextprotocol/sdk": "^1.12.3",
43 | "zod": "^3.25.64"
44 | },
45 | "devDependencies": {
46 | "@commitlint/cli": "^19.8.1",
47 | "@commitlint/config-conventional": "^19.8.1",
48 | "@modelcontextprotocol/inspector": "^0.14.2",
49 | "@types/node": "^22.14.1",
50 | "husky": "^9.1.7",
51 | "shx": "^0.4.0",
52 | "tsup": "^8.5.0",
53 | "tsx": "^4.20.3",
54 | "typescript": "^5.8.3"
55 | }
56 | }
57 |
```
--------------------------------------------------------------------------------
/src/utils/schemas.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from "zod";
2 | import { registry } from "zod/dist/types/v4";
3 |
4 | export const ComponentSchema = z.object({
5 | name: z.string(),
6 | type: z.string(),
7 | description: z.string().optional(),
8 | });
9 |
10 | export const BlockSchema = ComponentSchema.extend({
11 | name: z.string(),
12 | type: z.string(),
13 | description: z.string().optional(),
14 | });
15 |
16 | const ExampleSchema = z.object({
17 | name: z.string(),
18 | type: z.string(),
19 | description: z.string(),
20 | content: z.string(),
21 | });
22 |
23 | export const IndividualComponentSchema = ComponentSchema.extend({
24 | install: z.string(),
25 | content: z.string(),
26 | examples: z.array(ExampleSchema),
27 | });
28 |
29 | export const IndividualBlockSchema = BlockSchema.extend({
30 | install: z.string(),
31 | content: z.string(),
32 | examples: z.array(ExampleSchema),
33 | });
34 |
35 | export const ComponentDetailSchema = z.object({
36 | name: z.string(),
37 | type: z.string(),
38 | files: z.array(
39 | z.object({
40 | content: z.string(),
41 | })
42 | ),
43 | });
44 |
45 | export const BlockDetailSchema = z.object({
46 | name: z.string(),
47 | type: z.string(),
48 | registryDependencies: z.array(z.string()),
49 | files: z.array(
50 | z.object({
51 | content: z.string(),
52 | })
53 | ),
54 | });
55 |
56 | export const ExampleComponentSchema = z.object({
57 | name: z.string(),
58 | type: z.string(),
59 | description: z.string().optional(),
60 | registryDependencies: z.array(z.string()),
61 | });
62 |
63 | export const ExampleDetailSchema = z.object({
64 | name: z.string(),
65 | type: z.string(),
66 | description: z.string(),
67 | files: z.array(
68 | z.object({
69 | content: z.string(),
70 | })
71 | ),
72 | });
73 |
```
--------------------------------------------------------------------------------
/src/lib/categories.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Component category definitions
2 | export const componentCategories = {
3 | Ratings: [
4 | "star-rating-basic",
5 | "star-rating-fractions",
6 | "upvote-rating-basic",
7 | "upvote-rating-animated",
8 | "face-rating-basic",
9 | "face-rating-gradient",
10 | ],
11 | Images: ["image-viewer-basic", "image-viewer-motion", "image-carousel-basic"],
12 | Products: [
13 | "price-format-basic",
14 | "price-format-sale",
15 | "quantity-input-basic",
16 | "variant-color-selector-basic",
17 | "variant-selector-basic",
18 | "variant-selector-images",
19 | "variant-selector-multiple",
20 | ],
21 | Inputs: ["input-icon", "phone-number-input-basic"],
22 | };
23 |
24 | export const blocksCategories = {
25 | Addresses: ["address-01-block", "address-02-block"],
26 | Banners: [
27 | "banner-01-block",
28 | "banner-02-block",
29 | "banner-03-block",
30 | "banner-04-block",
31 | "banner-05-block",
32 | "banner-06-block",
33 | "banner-07-block",
34 | "banner-08-block",
35 | "banner-09-block",
36 | "banner-10-block",
37 | "banner-11-block",
38 | "banner-12-block",
39 | ],
40 | ProductCards: [
41 | "product-card-01-block",
42 | "product-card-02-block",
43 | "product-card-03-block",
44 | "product-card-04-block",
45 | "product-card-05-block",
46 | "product-card-06-block",
47 | "product-card-07-block",
48 | "product-card-08-block",
49 | "product-card-09-block",
50 | "product-card-10-block",
51 | "product-card-11-block",
52 | "product-card-12-block",
53 | "product-card-13-block",
54 | ],
55 | ProductVariants: [
56 | "product-variant-01-block",
57 | "product-variant-02-block",
58 | "product-variant-03-block",
59 | "product-variant-04-block",
60 | "product-variant-05-block",
61 | ],
62 | Reviews: [
63 | "review-01-block",
64 | "review-02-block",
65 | "review-03-block",
66 | "review-04-block",
67 | "review-05-block",
68 | "review-06-block",
69 | "review-07-block",
70 | "review-08-block",
71 | "review-09-block",
72 | "review-10-block",
73 | ],
74 | Carts: ["cart-01-block"],
75 | };
76 |
```
--------------------------------------------------------------------------------
/src/utils/api.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { mcpConfig } from "../lib/config.js";
2 | import {
3 | BlockDetailSchema,
4 | BlockSchema,
5 | ComponentDetailSchema,
6 | ComponentSchema,
7 | ExampleComponentSchema,
8 | ExampleDetailSchema,
9 | } from "./schemas.js";
10 |
11 | /**
12 | * Fetches all UI components from the registry.
13 | * @returns An array of components with their details.
14 | */
15 | export async function fetchUIComponents() {
16 | try {
17 | const response = await fetch(mcpConfig.registryFileUrl);
18 | if (!response.ok) {
19 | throw new Error(
20 | `Failed to fetch registry.json: ${response.statusText} - Status: ${response.status}`
21 | );
22 | }
23 | const data = await response.json();
24 |
25 | return data.registry
26 | .filter((item: any) => item.type === "registry:component")
27 | .map((item: any) => {
28 | try {
29 | return ComponentSchema.parse({
30 | name: item.name,
31 | type: item.type,
32 | description: item.description,
33 | });
34 | } catch (parseError) {
35 | return null;
36 | }
37 | });
38 | } catch (error) {
39 | return [];
40 | }
41 | }
42 |
43 | /**
44 | * Fetches all UI blocks from the registry.
45 | * @returns An array of UI blocks.
46 | */
47 | export async function fetchUIBlocks() {
48 | try {
49 | const response = await fetch(mcpConfig.registryFileUrl);
50 | if (!response.ok) {
51 | throw new Error(
52 | `Failed to fetch registry.json: ${response.statusText} - Status: ${response.status}`
53 | );
54 | }
55 | const data = await response.json();
56 |
57 | return data.registry
58 | .filter((item: any) => item.type === "registry:block")
59 | .map((item: any) => {
60 | try {
61 | return BlockSchema.parse({
62 | name: item.name,
63 | type: item.type,
64 | description: item.description,
65 | });
66 | } catch (parseError) {
67 | return null;
68 | }
69 | });
70 | } catch (error) {
71 | return [];
72 | }
73 | }
74 |
75 | /**
76 | * Fetches details for a specific component from the registry.
77 | * @param componentName The name of the component.
78 | * @returns The details of the component.
79 | */
80 | export async function fetchComponentDetails(componentName: string) {
81 | try {
82 | const response = await fetch(
83 | `${mcpConfig.registryUrl}/${componentName}.json`
84 | );
85 | if (!response.ok) {
86 | throw new Error(
87 | `Failed to fetch component ${componentName}: ${response.statusText}`
88 | );
89 | }
90 | const data = await response.json();
91 | return ComponentDetailSchema.parse(data);
92 | } catch (error) {
93 | console.error(`Error fetching component ${componentName}:`, error);
94 | throw error;
95 | }
96 | }
97 |
98 | /**
99 | * Fetches details for a specific component from the registry.
100 | * @param componentName The name of the component.
101 | * @returns The details of the component.
102 | */
103 | export async function fetchBlockDetails(blockName: string) {
104 | try {
105 | const response = await fetch(`${mcpConfig.registryUrl}/${blockName}.json`);
106 | if (!response.ok) {
107 | throw new Error(
108 | `Failed to fetch block ${blockName}: ${response.statusText}`
109 | );
110 | }
111 | const data = await response.json();
112 | return BlockDetailSchema.parse(data);
113 | } catch (error) {
114 | console.error(`Error fetching block ${blockName}:`, error);
115 | throw error;
116 | }
117 | }
118 |
119 | /**
120 | * Fetches example components from the registry.
121 | * @returns An array of example components.
122 | */
123 | export async function fetchExampleComponents() {
124 | try {
125 | const response = await fetch(mcpConfig.registryFileUrl);
126 | const data = await response.json();
127 |
128 | return data.registry
129 | .filter((item: any) => item.type === "registry:example")
130 | .map((item: any) => {
131 | return ExampleComponentSchema.parse({
132 | name: item.name,
133 | type: item.type,
134 | description: item.description,
135 | registryDependencies: item.registryDependencies,
136 | });
137 | });
138 | } catch (error) {
139 | console.error("Error fetching example components:", error);
140 | return [];
141 | }
142 | }
143 |
144 | /**
145 | * Fetches details for a specific example component.
146 | * @param exampleName The name of the example component.
147 | * @returns The details of the example component.
148 | */
149 | export async function fetchExampleDetails(exampleName: string) {
150 | try {
151 | const response = await fetch(`${mcpConfig.registryUrl}/${exampleName}`);
152 | if (!response.ok) {
153 | throw new Error(
154 | `Failed to fetch example details for ${exampleName}: ${response.statusText}`
155 | );
156 | }
157 | const data = await response.json();
158 | return ExampleDetailSchema.parse(data);
159 | } catch (error) {
160 | console.error(`Error fetching example details for ${exampleName}:`, error);
161 | throw error;
162 | }
163 | }
164 |
```
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import {
6 | IndividualComponentSchema,
7 | fetchComponentDetails,
8 | fetchBlockDetails,
9 | fetchExampleComponents,
10 | fetchExampleDetails,
11 | fetchUIBlocks,
12 | fetchUIComponents,
13 | IndividualBlockSchema,
14 | } from "./utils/index.js";
15 | import { formatComponentName } from "./utils/formatters.js";
16 | import { blocksCategories, componentCategories } from "./lib/categories.js";
17 |
18 | // Initialize the MCP Server
19 | const server = new McpServer({
20 | name: "@stackzero-labs/mcp",
21 | version: "0.2.0",
22 | });
23 |
24 | // Register the main tool for getting all components
25 | server.tool(
26 | "getUIComponents",
27 | "Provides a comprehensive list of all stackzero-labs/ui components.",
28 | {},
29 | async () => {
30 | try {
31 | const uiComponents = await fetchUIComponents();
32 |
33 | return {
34 | content: [
35 | {
36 | type: "text",
37 | text: JSON.stringify(uiComponents, null, 2),
38 | },
39 | ],
40 | };
41 | } catch (error) {
42 | return {
43 | content: [
44 | {
45 | type: "text",
46 | text: "Failed to fetch stackzero-labs/ui components",
47 | },
48 | ],
49 | isError: true,
50 | };
51 | }
52 | }
53 | );
54 |
55 | // Register the main tool for getting all blocks
56 | server.tool(
57 | "getUIBlocks",
58 | "Provides a comprehensive list of all stackzero-labs/ui blocks.",
59 | {},
60 | async () => {
61 | try {
62 | const uiBlocks = await fetchUIBlocks();
63 | return {
64 | content: [
65 | {
66 | type: "text",
67 | text: JSON.stringify(uiBlocks, null, 2),
68 | },
69 | ],
70 | };
71 | } catch (error) {
72 | return {
73 | content: [
74 | {
75 | type: "text",
76 | text: "Failed to fetch stackzero-labs/ui blocks",
77 | },
78 | ],
79 | };
80 | }
81 | }
82 | );
83 |
84 | /**
85 | * Creates a registry mapping component names to their example implementations.
86 | * @param exampleComponentList - The list of example components to process.
87 | * @returns A map where each key is a component name and the value is an array of example names.
88 | */
89 | function createComponentExampleRegistry(
90 | exampleComponentList: Array<{
91 | name: string;
92 | registryDependencies?: string[];
93 | }>
94 | ): Map<string, string[]> {
95 | const componentRegistry = new Map<string, string[]>();
96 |
97 | for (const exampleItem of exampleComponentList) {
98 | if (
99 | exampleItem.registryDependencies &&
100 | Array.isArray(exampleItem.registryDependencies)
101 | ) {
102 | for (const dependencyUrl of exampleItem.registryDependencies) {
103 | if (
104 | typeof dependencyUrl === "string" &&
105 | dependencyUrl.includes("stackzero.co")
106 | ) {
107 | const nameExtraction = dependencyUrl.match(/\/r\/([^\/]+)$/);
108 | if (nameExtraction && nameExtraction[1]) {
109 | const extractedComponentName = nameExtraction[1];
110 | if (!componentRegistry.has(extractedComponentName)) {
111 | componentRegistry.set(extractedComponentName, []);
112 | }
113 | if (
114 | !componentRegistry
115 | .get(extractedComponentName)
116 | ?.includes(exampleItem.name)
117 | ) {
118 | componentRegistry
119 | .get(extractedComponentName)
120 | ?.push(exampleItem.name);
121 | }
122 | }
123 | }
124 | }
125 | }
126 | }
127 | return componentRegistry;
128 | }
129 |
130 | /**
131 | * Fetches components and blocks by category, processing each category's components and blocks.
132 | * @param categoryComponents - The list of component names in the category.
133 | * @param categoryBlocks - The list of block names in the category.
134 | * @returns An object containing the processed components and blocks.
135 | */
136 | async function fetchComponentsByCategory(
137 | categoryComponents: string[],
138 | allComponents: any[],
139 | exampleNamesByComponent: Map<string, string[]>
140 | ) {
141 | const componentResults = [];
142 |
143 | for (const componentName of categoryComponents) {
144 | const component = allComponents.find((c) => c.name === componentName);
145 | if (!component) continue;
146 |
147 | try {
148 | const componentDetails = await fetchComponentDetails(componentName);
149 | const componentContent = componentDetails.files[0]?.content;
150 |
151 | const relevantExampleNames =
152 | exampleNamesByComponent.get(componentName) || [];
153 |
154 | // Generate installation instructions
155 | const installInstructions = `You can install the component/blocks using \
156 | shadcn/ui CLI. For example, with npx: npx shadcn@latest add \
157 | "https://ui.stackzero.co/r/${componentName}.json" (Rules: make sure the URL is wrapped in \
158 | double quotes. Once installed, \
159 | you can import the component like this: import { ${formatComponentName(
160 | component.name
161 | )} } from \
162 | "@/components/ui/${componentName}";`;
163 |
164 | const disclaimerText = `The code below is for context only. It helps you understand
165 | the component's props, types, and behavior. After installing, the component
166 | will be available for import via: import { ${formatComponentName(
167 | component.name
168 | )} } \
169 | from "@/components/ui/${componentName}";`;
170 |
171 | const exampleDetailsList = await Promise.all(
172 | relevantExampleNames.map((name) => fetchExampleDetails(name))
173 | );
174 |
175 | const formattedExamples = exampleDetailsList
176 | .filter((details) => details !== null)
177 | .map((details) => ({
178 | name: details.name,
179 | type: details.type,
180 | description: details.description,
181 | content: details.files[0]?.content,
182 | }));
183 |
184 | const validatedComponent = IndividualComponentSchema.parse({
185 | name: component.name,
186 | type: component.type,
187 | description: component.description,
188 | install: installInstructions,
189 | content: componentContent && disclaimerText + componentContent,
190 | examples: formattedExamples,
191 | });
192 |
193 | componentResults.push(validatedComponent);
194 | } catch (error) {
195 | console.error(`Error processing component ${componentName}:`, error);
196 | }
197 | }
198 |
199 | return componentResults;
200 | }
201 |
202 | /**
203 | * Fetches blocks by category, processing each block in the category.
204 | * @param categoryBlocks - The list of block names in the category.
205 | * @param allBlocks - The complete list of blocks to search from.
206 | * @returns An array of processed blocks.
207 | */
208 | async function fetchBlocksByCategory(
209 | categoryBlocks: string[],
210 | allBlocks: any[]
211 | ) {
212 | const blockResults = [];
213 |
214 | for (const blockName of categoryBlocks) {
215 | const block = allBlocks.find((b) => b.name === blockName);
216 | if (!block) continue;
217 |
218 | try {
219 | const blockDetails = await fetchBlockDetails(blockName);
220 | const blockContent = blockDetails.files[0]?.content;
221 |
222 | // Generate installation instructions
223 | const installInstructions = `You can install the blocks using \
224 | shadcn/ui CLI. For example, with npx: npx shadcn@latest add \
225 | "https://ui.stackzero.co/r/${blockName}.json" (Rules: make sure the URL is wrapped in \
226 | double quotes. Once installed, \
227 | you can import the block like this: import { ${formatComponentName(
228 | block.name
229 | )} } from \
230 | "@/components/ui/${blockName}";`;
231 |
232 | const disclaimerText = `The code below is for context only. It helps you understand
233 | the block's props, types, and behavior. After installing, the block
234 | will be available for import via: import { ${formatComponentName(
235 | block.name
236 | )} } \
237 | from "@/components/ui/${blockName}";`;
238 |
239 | const validatedBlock = IndividualBlockSchema.parse({
240 | name: block.name,
241 | type: block.type,
242 | description: block.description,
243 | install: installInstructions,
244 | content: blockContent && disclaimerText + blockContent,
245 | examples: [], // Blocks typically don't have examples, but can be added if needed
246 | });
247 |
248 | blockResults.push(validatedBlock);
249 | } catch (error) {
250 | console.error(`Error processing block ${blockName}:`, error);
251 | }
252 | }
253 |
254 | return blockResults;
255 | }
256 |
257 | // Registers tools for each component category
258 | async function registerComponentsCategoryTools() {
259 | const [components, allExampleComponents] = await Promise.all([
260 | fetchUIComponents(),
261 | fetchExampleComponents(),
262 | ]);
263 |
264 | const exampleNamesByComponent =
265 | createComponentExampleRegistry(allExampleComponents);
266 |
267 | // console.error(
268 | // "🔍 Found example names by component:",
269 | // exampleNamesByComponent
270 | // );
271 |
272 | for (const [category, categoryComponents] of Object.entries(
273 | componentCategories
274 | )) {
275 | const componentNamesString = categoryComponents.join(", ");
276 |
277 | server.tool(
278 | `get${category}`,
279 | `Provides implementation details for ${componentNamesString} components.`,
280 | {},
281 | async () => {
282 | try {
283 | const categoryResults = await fetchComponentsByCategory(
284 | categoryComponents,
285 | components,
286 | exampleNamesByComponent
287 | );
288 |
289 | return {
290 | content: [
291 | {
292 | type: "text",
293 | text: JSON.stringify(categoryResults, null, 2),
294 | },
295 | ],
296 | };
297 | } catch (error) {
298 | let errorMessage = `Error processing ${category} components`;
299 | if (error instanceof Error) {
300 | errorMessage += `: ${error.message}`;
301 | }
302 | return {
303 | content: [{ type: "text", text: errorMessage }],
304 | isError: true,
305 | };
306 | }
307 | }
308 | );
309 | }
310 | }
311 |
312 | async function registerBlocksCategoryTools() {
313 | const [blocks] = await Promise.all([fetchUIBlocks()]);
314 |
315 | // console.error(
316 | // "🔍 Found example names by component:",
317 | // exampleNamesByComponent
318 | // );
319 |
320 | for (const [category, categoryBlocks] of Object.entries(blocksCategories)) {
321 | const blockNamesString = categoryBlocks.join(", ");
322 |
323 | server.tool(
324 | `get${category}`,
325 | `Provides implementation details for ${blockNamesString} blocks.`,
326 | {},
327 | async () => {
328 | try {
329 | const categoryResults = await fetchBlocksByCategory(
330 | categoryBlocks,
331 | blocks
332 | );
333 |
334 | return {
335 | content: [
336 | {
337 | type: "text",
338 | text: JSON.stringify(categoryResults, null, 2),
339 | },
340 | ],
341 | };
342 | } catch (error) {
343 | let errorMessage = `Error processing ${category} blocks`;
344 | if (error instanceof Error) {
345 | errorMessage += `: ${error.message}`;
346 | }
347 | return {
348 | content: [{ type: "text", text: errorMessage }],
349 | isError: true,
350 | };
351 | }
352 | }
353 | );
354 | }
355 | }
356 |
357 | // Start the MCP server
358 | async function startServer() {
359 | try {
360 | // Initialize category tools first
361 | await registerComponentsCategoryTools();
362 | await registerBlocksCategoryTools();
363 | // Connect to stdio transport
364 | const transport = new StdioServerTransport();
365 | await server.connect(transport);
366 | } catch (error) {
367 | console.error("❌ Error starting MCP server:", error);
368 |
369 | // Try to start server anyway with basic functionality
370 | try {
371 | const transport = new StdioServerTransport();
372 | await server.connect(transport);
373 | console.error("⚠️ MCP server started with limited functionality");
374 | } catch (connectionError) {
375 | console.error("❌ Failed to connect to transport:", connectionError);
376 | process.exit(1);
377 | }
378 | }
379 | }
380 |
381 | // Start the server
382 | startServer();
383 |
```