feat(shadcn): mcp init (#8086)

This commit is contained in:
shadcn
2025-08-27 12:05:21 +04:00
committed by GitHub
parent fc27ba2692
commit 17422714f6
3 changed files with 206 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": major
---
add mcp init command

View File

@@ -1,13 +1,70 @@
import { promises as fs } from "fs"
import path from "path"
import { server } from "@/src/mcp"
import { loadEnvFiles } from "@/src/utils/env-loader"
import { getConfig } from "@/src/utils/get-config"
import { getPackageManager } from "@/src/utils/get-package-manager"
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { Command } from "commander"
import deepmerge from "deepmerge"
import { execa } from "execa"
import fsExtra from "fs-extra"
import prompts from "prompts"
import z from "zod"
const SHADCN_MCP_VERSION = "beta"
const CLIENTS = [
{
name: "claude",
label: "Claude Code",
configPath: ".mcp.json",
config: {
mcpServers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
{
name: "cursor",
label: "Cursor",
configPath: ".cursor/mcp.json",
config: {
mcpServers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
{
name: "vscode",
label: "VS Code",
configPath: ".vscode/mcp.json",
config: {
servers: {
shadcn: {
command: "npx",
args: [`shadcn@${SHADCN_MCP_VERSION}`, "mcp"],
},
},
},
},
] as const
const DEPENDENCIES = [`shadcn@${SHADCN_MCP_VERSION}`]
export const mcp = new Command()
.name("mcp")
.description("starts the registry MCP server")
.description("MCP server and configuration commands")
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
@@ -23,3 +80,119 @@ export const mcp = new Command()
handleError(error)
}
})
const mcpInitOptionsSchema = z.object({
client: z.enum(["claude", "cursor", "vscode"]),
cwd: z.string(),
})
mcp
.command("init")
.description("Initialize MCP configuration for your client")
.option(
"--client <client>",
`MCP client (${CLIENTS.map((c) => c.name).join(", ")})`
)
.action(async (opts, command) => {
try {
// Get the cwd from parent command.
const parentOpts = command.parent?.opts() || {}
const cwd = parentOpts.cwd || process.cwd()
let client = opts.client
if (!client) {
const response = await prompts({
type: "select",
name: "client",
message: "Which MCP client are you using?",
choices: CLIENTS.map((c) => ({
title: c.label,
value: c.name,
})),
})
if (!response.client) {
logger.break()
process.exit(1)
}
client = response.client
}
const options = mcpInitOptionsSchema.parse({
client,
cwd,
})
const configSpinner = spinner("Configuring MCP server...").start()
const configPath = await runMcpInit(options)
configSpinner.succeed("Configuring MCP server.")
const config = await getConfig(options.cwd)
if (config) {
await updateDependencies([], DEPENDENCIES, config, {
silent: false,
})
} else {
const packageManager = await getPackageManager(options.cwd)
const installCommand = packageManager === "npm" ? "install" : "add"
const devFlag = packageManager === "npm" ? "--save-dev" : "-D"
const installSpinner = spinner("Installing dependencies...").start()
await execa(
packageManager,
[installCommand, devFlag, ...DEPENDENCIES],
{
cwd: options.cwd,
}
)
installSpinner.succeed("Installing dependencies.")
}
logger.break()
logger.success(`Configuration saved to ${configPath}.`)
logger.break()
} catch (error) {
handleError(error)
}
})
const overwriteMerge = (_: any[], sourceArray: any[]) => sourceArray
async function runMcpInit(options: z.infer<typeof mcpInitOptionsSchema>) {
const { client, cwd } = options
const clientInfo = CLIENTS.find((c) => c.name === client)
if (!clientInfo) {
throw new Error(
`Unknown client: ${client}. Available clients: ${CLIENTS.map(
(c) => c.name
).join(", ")}`
)
}
const configPath = path.join(cwd, clientInfo.configPath)
let existingConfig = {}
try {
const content = await fs.readFile(configPath, "utf-8")
existingConfig = JSON.parse(content)
} catch {}
const mergedConfig = deepmerge(
existingConfig,
clientInfo.config as Record<string, unknown>,
{ arrayMerge: overwriteMerge }
)
const dir = path.dirname(configPath)
await fsExtra.ensureDir(dir)
await fs.writeFile(
configPath,
JSON.stringify(mergedConfig, null, 2) + "\n",
"utf-8"
)
return clientInfo.configPath
}

View File

@@ -135,6 +135,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
})
),
},
{
name: "get_audit_checklist",
description:
"After creating new components or generating new code files, use this tool for a quick checklist to verify that everything is working as expected. Make sure to run the tool after all required steps have been completed.",
inputSchema: zodToJsonSchema(z.object({})),
},
],
}
})
@@ -380,6 +386,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
}
case "get_audit_checklist": {
return {
content: [
{
type: "text",
text: dedent`## Component Audit Checklist
After adding or generating components, check the following common issues:
- [ ] Ensure imports are correct i.e named vs default imports
- [ ] If using next/image, ensure images.remotePatterns next.config.js is configured correctly.
- [ ] Ensure all dependencies are installed.
- [ ] Check for linting errors or warnings
- [ ] Check for TypeScript errors
- [ ] Use the Playwright MCP if available.
`,
},
],
}
}
default:
throw new Error(`Tool ${request.params.name} not found`)
}