Skip to content

Commit 9b9929a

Browse files
authored
feat: add MCP server (#44)
* feat: add MCP server * refactor: improve mcp routes * docs: add MCP server documentation
1 parent 75d7e15 commit 9b9929a

File tree

7 files changed

+2028
-408
lines changed

7 files changed

+2028
-408
lines changed

apps/registry/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"build": "nitro build",
77
"dev": "nitro dev --port 3001"
88
},
9+
"dependencies": {
10+
"@modelcontextprotocol/sdk": "^1.22.0",
11+
"mcp-handler": "^1.0.3",
12+
"shadcn-vue": "^2.3.2",
13+
"zod": "~3.25.76"
14+
},
915
"devDependencies": {
1016
"@vue/compiler-sfc": "^3.5.22",
1117
"h3": "^1.15.4",

apps/registry/server/routes/mcp.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { Registry, RegistryItem } from 'shadcn-vue/schema'
2+
import type { ZodRawShape } from 'zod'
3+
import { fromWebHandler } from 'h3'
4+
import { createMcpHandler } from 'mcp-handler'
5+
import { useStorage } from 'nitropack/runtime'
6+
import { z } from 'zod'
7+
8+
const REGISTRY_STORAGE_BASE = 'assets:registry'
9+
const REGISTRY_INDEX_FILE = 'index.json'
10+
11+
// Parameter Schemas
12+
const componentParamsShape = {
13+
component: z.string().min(1, 'component is required'),
14+
} satisfies ZodRawShape
15+
16+
const componentParamsSchema = z.object(componentParamsShape)
17+
18+
// Data Access Layer
19+
function getRegistryStorage() {
20+
return useStorage(REGISTRY_STORAGE_BASE)
21+
}
22+
23+
async function loadRegistryIndex(): Promise<Registry | null> {
24+
try {
25+
const storage = getRegistryStorage()
26+
return await storage.getItem(REGISTRY_INDEX_FILE) as Registry
27+
}
28+
catch (error) {
29+
console.error('Failed to read registry index', error)
30+
return null
31+
}
32+
}
33+
34+
async function listComponentNames(): Promise<string[]> {
35+
const index = await loadRegistryIndex()
36+
if (!index?.items) {
37+
return []
38+
}
39+
40+
return index.items
41+
.filter(item => item.type === 'registry:component')
42+
.map(item => item.name)
43+
.sort((a, b) => a.localeCompare(b))
44+
}
45+
46+
async function loadRegistryItem(name: string): Promise<RegistryItem | null> {
47+
const storage = getRegistryStorage()
48+
// Normalize and sanitize input
49+
const normalized = name.replace(/\.json$/i, '')
50+
51+
try {
52+
const componentJson = await storage.getItem(`components/${normalized}.json`) as RegistryItem
53+
if (componentJson) {
54+
return componentJson
55+
}
56+
}
57+
catch (error) {
58+
console.error(`Failed to read component ${normalized}.json`, error)
59+
}
60+
61+
return null
62+
}
63+
64+
// Tool Handlers
65+
async function handleListComponents() {
66+
const componentNames = await listComponentNames()
67+
const body = componentNames.length
68+
? JSON.stringify(componentNames, null, 2)
69+
: '[]'
70+
71+
return {
72+
content: [{ type: 'text' as const, text: body }],
73+
}
74+
}
75+
76+
async function handleGetComponent(args: Record<string, unknown>) {
77+
const parsedArgs = componentParamsSchema.safeParse(args)
78+
if (!parsedArgs.success) {
79+
return {
80+
content: [{ type: 'text' as const, text: `Invalid input: ${parsedArgs.error.message}` }],
81+
isError: true,
82+
}
83+
}
84+
85+
const { component } = parsedArgs.data
86+
const registryItem = await loadRegistryItem(component)
87+
88+
if (!registryItem) {
89+
return {
90+
content: [{ type: 'text' as const, text: `Component "${component}" not found.` }],
91+
isError: true,
92+
}
93+
}
94+
95+
return {
96+
content: [{ type: 'text' as const, text: JSON.stringify(registryItem, null, 2) }],
97+
}
98+
}
99+
100+
// Main Handler
101+
const handler = createMcpHandler(
102+
(server) => {
103+
server.registerTool(
104+
'get_ai_elements_components',
105+
{
106+
title: 'List AI Elements components',
107+
description: 'Provides a list of all AI Elements components.',
108+
},
109+
handleListComponents,
110+
)
111+
112+
server.registerTool(
113+
'get_ai_elements_component',
114+
{
115+
title: 'Get AI Elements component',
116+
description: 'Provides information about an AI Elements component.',
117+
inputSchema: componentParamsShape,
118+
},
119+
handleGetComponent,
120+
)
121+
},
122+
)
123+
124+
export default fromWebHandler(handler)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
title: MCP Server
3+
description: AI Elements Vue supports the Model Context Protocol (MCP) for model-driven development.
4+
icon: lucide:server
5+
---
6+
7+
The **Model Context Protocol (MCP)** is an open standard that allows AI assistants like Claude, Cursor, and other AI-powered tools to securely connect with external data sources and systems. Think of it as a "universal remote" that lets your AI tools access real-world data and functionality.
8+
9+
AI Elements Vue supports MCP to supercharge your AI development workflow.
10+
11+
## Installation Guide
12+
13+
::steps
14+
### Step 1: Choose Your AI Tool
15+
16+
First, make sure you're using an AI development tool that supports MCP:
17+
18+
- [Claude Desktop](https://claude.com/download) (Free - recommended for beginners)
19+
- [Claude Code](https://www.claude.com/product/claude-code) (AI coding assistant)
20+
- [Cursor](https://cursor.com) (AI-powered code editor)
21+
- [Windsurf by Codeium](https://windsurf.com) (AI development platform)
22+
- Other MCP-compatible tools
23+
24+
### Step 2: Locate Your Configuration File
25+
26+
Depending on your AI tool, you'll need to edit one of these files:
27+
28+
- **Claude Desktop**: `Claude/claude_desktop_config.json`
29+
- **Claude Code**: `.claude.json`
30+
- **Cursor** `.cursor/mcp.json`
31+
- **Windsurf**: `.codeium/windsurf/mcp_config.json`
32+
- **Other tools**: Check your tool's MCP documentation
33+
34+
### Step 3: Add AI Elements Vue Configuration
35+
36+
#### Option A: Manual Configuration (All Tools)
37+
38+
Copy and paste this configuration into your MCP config file:
39+
40+
```json [.cursor/mcp.json]
41+
{
42+
"mcpServers": {
43+
"ai-elements-vue": {
44+
"command": "npx",
45+
"args": [
46+
"-y",
47+
"mcp-remote",
48+
"https://registry.ai-elements-vue.com/mcp"
49+
]
50+
}
51+
}
52+
}
53+
```
54+
55+
#### Option B: Quick Install (Claude Code Only)
56+
57+
If you're using Claude Code, you can use the built-in command:
58+
59+
```bash
60+
claude mcp add --transport http ai-elements-vue https://registry.ai-elements-vue.com/mcp
61+
```
62+
63+
This automatically adds the server to your .claude.json configuration.
64+
65+
### Step 4: Restart Your AI Tool
66+
67+
Close and reopen your AI application for the changes to take effect.
68+
69+
### Step 5: Verify the Connection
70+
71+
Test the integration by asking your AI assistant:
72+
73+
> **"What AI Elements Vue components are available for building an AI app?"**
74+
75+
If successful, your AI should be able to list and explain AI Elements Vue components!
76+
::
77+
78+
## Multiple MCP Servers
79+
80+
You can use AI Elements Vue alongside other MCP servers:
81+
82+
```json [.cursor/mcp.json]
83+
{
84+
"mcpServers": {
85+
"ai-elements-vue": {
86+
"command": "npx",
87+
"args": ["-y", "mcp-remote", "https://registry.ai-elements-vue.com/mcp"]
88+
},
89+
"github": {
90+
"command": "npx",
91+
"args": ["-y", "@modelcontextprotocol/server-github"]
92+
},
93+
"filesystem": {
94+
"command": "npx",
95+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
96+
}
97+
}
98+
}
99+
```
100+
101+
## Usage Examples
102+
103+
### Getting Component Information
104+
105+
Ask your AI assistant:
106+
107+
> **"Show me how to use the AI Elements Vue PromptInput component with different variants"**
108+
109+
Expected response will include current documentation and code examples.
110+
111+
### Building Layouts
112+
113+
> **"Help me create an AI app layout using AI Elements Vue components"**
114+
115+
Your AI can suggest appropriate layout components and provide implementation code.
116+
117+
### Styling Guidance
118+
119+
> **"What are the recommended spacing tokens in AI Elements Vue?"**
120+
121+
Get up-to-date information about design tokens and styling conventions.
122+
123+
## Security and Privacy
124+
125+
### Data Handling
126+
127+
- The AI Elements Vue MCP server only provides public component documentation
128+
- No personal data or code is transmitted to our servers
129+
- All communication happens through your chosen AI tool's security layer
130+
131+
### Authentication
132+
133+
- No authentication required for public component information
134+
- Future premium features may require API keys
135+
- Always use official AI Elements Vue MCP endpoints

apps/www/plugins/ai-elements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ import {
3737
Suggestion,
3838
SuggestionAiInput,
3939
Task,
40-
WebPreview,
4140
Tool,
4241
ToolInputAvailable,
4342
ToolInputStreaming,
4443
ToolOutputAvailable,
4544
ToolOutputError,
45+
WebPreview,
4646
Workflow,
4747
} from '@repo/examples'
4848

packages/elements/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ export * from './shimmer'
2222
export * from './sources'
2323
export * from './suggestion'
2424
export * from './task'
25-
export * from './web-preview'
2625
export * from './tool'
26+
export * from './web-preview'

packages/examples/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ export { default as Sources } from './sources.vue'
3636
export { default as SuggestionAiInput } from './suggestion-ai-input.vue'
3737
export { default as Suggestion } from './suggestion.vue'
3838
export { default as Task } from './task.vue'
39-
export { default as WebPreview } from './web-preview.vue'
4039
export { default as ToolInputAvailable } from './tool-input-available.vue'
4140
export { default as ToolInputStreaming } from './tool-input-streaming.vue'
4241
export { default as ToolOutputAvailable } from './tool-output-available.vue'
4342
export { default as ToolOutputError } from './tool-output-error.vue'
4443
export { default as Tool } from './tool.vue'
44+
export { default as WebPreview } from './web-preview.vue'
4545
export { default as Workflow } from './workflow.vue'

0 commit comments

Comments
 (0)