Skip to content

Commit 00f4509

Browse files
committed
fix(registry): remove mcp-handler and update MCP route
1 parent 0e31939 commit 00f4509

File tree

5 files changed

+97
-146
lines changed

5 files changed

+97
-146
lines changed

apps/registry/nitro.config.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@ import { buildHooks } from './server/hooks'
33

44
// https://nitro.build/config
55
export default defineNitroConfig({
6-
compatibilityDate: '2024-09-19',
6+
compatibilityDate: 'latest',
77
srcDir: 'server',
8-
preset: 'cloudflare-module',
8+
preset: 'cloudflare',
99
hooks: buildHooks,
1010
serverAssets: [
1111
{
1212
baseName: 'registry',
1313
dir: './assets/registry',
1414
},
1515
],
16+
cloudflare: {
17+
nodeCompat: true,
18+
deployConfig: true,
19+
},
20+
unenv: {
21+
alias: {
22+
// https://github.com/nitrojs/nitro/issues/3170
23+
'safer-buffer': 'node:buffer',
24+
},
25+
},
1626
})

apps/registry/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
},
99
"dependencies": {
1010
"@modelcontextprotocol/sdk": "^1.22.0",
11-
"mcp-handler": "^1.0.3",
1211
"shadcn-vue": "^2.3.2",
1312
"zod": "~3.25.76"
1413
},

apps/registry/server/routes/mcp.ts

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1+
import type { H3Event } from 'h3'
12
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'
3+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
5+
import { defineEventHandler, getHeader, readBody, sendRedirect } from 'h3'
56
import { useStorage } from 'nitropack/runtime'
67
import { z } from 'zod'
78

89
const REGISTRY_STORAGE_BASE = 'assets:registry'
10+
911
const REGISTRY_INDEX_FILE = 'index.json'
1012

11-
// Parameter Schemas
12-
const componentParamsShape = {
13-
component: z.string().min(1, 'component is required'),
14-
} satisfies ZodRawShape
13+
const SERVER_INFO = {
14+
name: 'ai-elements-vue',
15+
version: '1.0.0',
16+
}
17+
18+
const DOCS_REDIRECT = 'https://www.ai-elements-vue.com/overview/mcp-server'
19+
20+
const getComponentInputSchema = z.object({
21+
component: z
22+
.string()
23+
.min(1, 'component is required')
24+
.describe('Component name (e.g. "context")'),
25+
})
1526

16-
const componentParamsSchema = z.object(componentParamsShape)
27+
type GetComponentInput = z.infer<typeof getComponentInputSchema>
1728

1829
// Data Access Layer
1930
function getRegistryStorage() {
@@ -73,16 +84,7 @@ async function handleListComponents() {
7384
}
7485
}
7586

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
87+
async function handleGetComponent(component: string) {
8688
const registryItem = await loadRegistryItem(component)
8789

8890
if (!registryItem) {
@@ -97,28 +99,67 @@ async function handleGetComponent(args: Record<string, unknown>) {
9799
}
98100
}
99101

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)
102+
function createMcpServer() {
103+
const server = new McpServer(SERVER_INFO)
104+
105+
server.registerTool(
106+
'get_ai_elements_components',
107+
{
108+
title: 'List AI Elements components',
109+
description: 'Provides a list of all AI Elements components.',
110+
},
111+
async () => handleListComponents(),
112+
)
113+
114+
server.registerTool(
115+
'get_ai_elements_component',
116+
{
117+
title: 'Get AI Elements component',
118+
description: 'Provides information about an AI Elements component.',
119+
inputSchema: getComponentInputSchema,
120+
},
121+
async ({ component }: GetComponentInput) => handleGetComponent(component),
122+
)
123+
124+
return server
125+
}
126+
127+
async function readOptionalBody(event: H3Event) {
128+
const method = event.node.req.method
129+
if (!method || method === 'GET' || method === 'HEAD') {
130+
return undefined
131+
}
132+
133+
try {
134+
return await readBody(event)
135+
}
136+
catch (error) {
137+
console.warn('Failed to parse MCP request body, falling back to undefined', error)
138+
return undefined
139+
}
140+
}
141+
142+
export default defineEventHandler(async (event) => {
143+
if (event.node.req.method === 'GET') {
144+
const accept = getHeader(event, 'accept') ?? ''
145+
if (accept.includes('text/html')) {
146+
return sendRedirect(event, DOCS_REDIRECT)
147+
}
148+
}
149+
150+
const server = createMcpServer()
151+
const transport = new StreamableHTTPServerTransport({
152+
sessionIdGenerator: undefined,
153+
})
154+
155+
event.node.res.once('close', () => {
156+
server.close().catch(error => console.error('Failed to close MCP server', error))
157+
if (typeof transport.close === 'function') {
158+
transport.close().catch(error => console.error('Failed to close MCP transport', error))
159+
}
160+
})
161+
162+
const body = await readOptionalBody(event)
163+
await server.connect(transport)
164+
await transport.handleRequest(event.node.req, event.node.res, body)
165+
})

apps/registry/wrangler.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
name = "ai-elements-vue-registry"
22
main = ".output/server/index.mjs"
3-
compatibility_date = "2025-09-19"
4-
compatibility_flags = [ "nodejs_compat" ]
3+
compatibility_date = "2025-11-26"
54
routes = [ "registry.ai-elements-vue.com/*" ]

pnpm-lock.yaml

Lines changed: 0 additions & 98 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)