vendure_add
Extend Vendure projects by adding API extensions, entities, services, job queues, or custom plugins. Specify plugin, entity, or service details to integrate new features seamlessly into your e-commerce setup.
Instructions
Add features to your Vendure project.
IMPORTANT USAGE PATTERNS:
For API Extension: Requires apiExtension="plugin-name", plus queryName OR mutationName, plus selectedService
For Entity: Requires entity="EntityName" and selectedPlugin="plugin-name"
For Service: Requires service="ServiceName" and selectedPlugin="plugin-name"
For Job Queue: Requires jobQueue="plugin-name", name="queue-name", and selectedService="service-name"
EXAMPLES:
Add API extension: {apiExtension: "my-plugin", queryName: "customProducts", selectedService: "ProductService"}
Add entity: {entity: "CustomProduct", selectedPlugin: "my-plugin"}
Add service: {service: "CustomService", selectedPlugin: "my-plugin"}
Create new plugin: {plugin: "MyNewPlugin"}
Use list_plugins tool first to see available plugin names.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apiExtension | No | Add an API extension scaffold to the specified plugin. Provide the plugin name. Example: "my-plugin". Requires queryName or mutationName and selectedService. | |
| codegen | No | Add GraphQL codegen configuration to the specified plugin. Provide the plugin name. Example: "my-plugin" | |
| config | No | Specify the path to a custom Vendure config file. Example: "./custom-vendure-config.ts" | |
| customFields | No | Add custom fields support to the entity (boolean flag) | |
| entity | No | Add a new entity with the specified class name. Example: "Product" or "Customer". Requires selectedPlugin to be specified. | |
| jobQueue | No | Add job-queue support to the specified plugin. Provide the plugin name. Example: "my-plugin" | |
| mutationName | No | Name for the GraphQL mutation (used with apiExtension). Example: "createCustomOrder" or "updateSpecialPrice" | |
| name | No | Name for the job queue (required with jobQueue). Example: "email-queue" or "product-import-queue" | |
| plugin | No | Create a new plugin with the specified name. Example: "MyNewPlugin" | |
| projectPath | Yes | Path to the Vendure project directory (required) | |
| queryName | No | Name for the GraphQL query (used with apiExtension). Example: "customProducts" or "getSpecialOffers" | |
| selectedEntity | No | Name of the entity for entity service (automatically sets type to entity). Example: "Product" | |
| selectedPlugin | No | Name of the plugin to add the entity/service/api-extension to. Must be an existing plugin name. Example: "my-plugin" or "test-plugin" | |
| selectedService | No | Name of the service to add the job queue or API extension to. Must be an existing service. Example: "ProductService" | |
| service | No | Add a new service with the specified class name. Example: "ProductService" or "OrderService". Requires selectedPlugin to be specified. | |
| translatable | No | Make the entity translatable (boolean flag) | |
| type | No | Type of service: "basic" or "entity" (default: basic). Use "entity" when working with database entities. | |
| uiExtensions | No | Add Admin UI extensions setup to the specified plugin. Provide the plugin name. Example: "my-plugin" |
Implementation Reference
- src/commands/tool-registry.ts:13-62 (registration)Registers vendure_add and other Vendure CLI commands as MCP tools by iterating over cliCommands and calling server.registerTool with name `vendure_${command.name}` (e.g. vendure_add for command 'add')
export function registerCliCommandTools(server: McpServer): void { for (const command of cliCommands) { const commandSchema = commandSchemas[command.name]; // Register main command server.registerTool( `vendure_${command.name}`, { description: command.description, ...(command.options && command.options.length > 0 && { inputSchema: commandSchema.mainCommand.shape }), }, async (args: Record<string, any>) => { const { projectPath } = getProjectContext(); const result = await executeMcpOperation(command.name, args, projectPath); return { content: [{ type: 'text' as const, text: result }], }; }, ); // Register sub-commands if (commandSchema.subCommands) { for (const subCommandName of Object.keys(commandSchema.subCommands)) { const subCommandSchema = commandSchema.subCommands[subCommandName]; const subCommandOption = command.options?.find( o => convertToParameterName(o.long) === subCommandName, ); server.registerTool( `vendure_${command.name}_${convertCamelToSnakeCase(subCommandName)}`, { description: `${subCommandOption?.description} (used in "vendure ${command.name}")`, inputSchema: subCommandSchema.shape, }, async (args: Record<string, any>) => { const { projectPath } = getProjectContext(); // Handle both 'name' and 'value' as the main parameter (in case of naming conflicts) const mainParamValue = args.name ?? args.value; const transformedArgs = { [subCommandName]: mainParamValue, ...args }; const result = await executeMcpOperation(command.name, transformedArgs, projectPath); return { content: [{ type: 'text' as const, text: result }], }; }, ); } } } } - Dynamically generates Zod schemas for input validation of vendure_add (and all CLI tools) from the Vendure CLI command definitions
export const commandSchemas: Record<string, any> = cliCommands.reduce( (acc, command) => { acc[command.name] = generateSchemaFromCommand(command); return acc; }, {} as Record<string, any>, ); - src/utils/cli-executor.ts:54-67 (handler)Core handler logic for vendure_add: formats arguments, spawns the Vendure CLI binary with 'add' command, captures output, and returns result (called from tool handler)
export async function executeMcpOperation( commandName: string, options: Record<string, any>, projectPath: string, ): Promise<string> { try { const cliArgs = [commandName, ...formatOptionsForCli(options)]; const result = await executeVendureCommand(cliArgs, projectPath); return `${commandName} operation completed successfully.\n\nOutput:\n${result}`; } catch (error) { throw new Error( `Failed to execute ${commandName}: ${error instanceof Error ? error.message : String(error)}`, ); } - src/utils/cli-executor.ts:70-87 (helper)Helper to convert tool input parameters (camelCase) to kebab-case CLI flags for vendure add command
function formatOptionsForCli(options: Record<string, any>): string[] { const args: string[] = []; for (const [key, value] of Object.entries(options)) { if (value === undefined || value === false) continue; const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase(); const flag = `--${kebabKey}`; if (typeof value === 'boolean' && value === true) { args.push(flag); } else if (typeof value === 'string' || typeof value === 'number') { args.push(flag, String(value)); } } return args; } - src/utils/cli-executor.ts:5-52 (helper)Low-level helper that spawns the vendure CLI process and captures stdout/stderr for the add command execution
async function executeVendureCommand(args: string[], projectPath: string): Promise<string> { return new Promise((resolve, reject) => { const vendureBin = path.join(projectPath, 'node_modules', '.bin', 'vendure'); if (!fs.existsSync(projectPath)) { reject(new Error(`Project directory does not exist: ${projectPath}`)); return; } if (!fs.existsSync(vendureBin)) { reject( new Error( `Vendure CLI not found at: ${vendureBin}. Make sure @vendure/cli is installed in the project.`, ), ); return; } const child = spawn(vendureBin, args, { cwd: projectPath, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env }, }); let stdout = ''; let stderr = ''; child.stdout?.on('data', data => { stdout += data.toString(); }); child.stderr?.on('data', data => { stderr += data.toString(); }); child.on('close', code => { if (code === 0) { resolve(stdout); } else { reject(new Error(stderr || stdout || `Command exited with code ${code}`)); } }); child.on('error', err => { reject(err); }); }); }