get_best_practice_guide
Access SFCC best practice guides to implement features correctly, covering cartridges, templates, hooks, controllers, endpoints, performance, and security.
Instructions
Get a complete best practice and how-to guide with all sections and content. Use this when implementing specific SFCC features like cartridges, ISML templates, OCAPI/SCAPI hooks, SFRA controllers, or custom endpoints. Always consult the relevant guide before writing code to ensure you follow SFCC best practices, security guidelines, and proper architecture patterns.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| guideName | Yes | The guide name (e.g., 'cartridge_creation', 'isml_templates', 'job_framework', 'localserviceregistry', 'ocapi_hooks', 'scapi_hooks', 'sfra_controllers', 'sfra_models', 'sfra_client_side_js', 'sfra_scss', 'scapi_custom_endpoint', 'performance', 'security') |
Implementation Reference
- The handler configuration for the 'get_best_practice_guide' tool. It includes defaults, input validation requiring 'guideName', the exec function that retrieves the SFCCBestPracticesClient from context and calls getBestPracticeGuide, and a log message.
get_best_practice_guide: { defaults: (args: ToolArguments) => args, validate: (args: ToolArguments, toolName: string) => { ValidationHelpers.validateArguments(args, CommonValidations.requiredString('guideName'), toolName); }, exec: async (args: ToolArguments, context: ToolExecutionContext) => { const client = context.bestPracticesClient as SFCCBestPracticesClient; return client.getBestPracticeGuide(args.guideName as string); }, logMessage: (args: ToolArguments) => `Guide ${args.guideName}`, }, - src/core/tool-definitions.ts:118-132 (schema)The tool schema definition in the BEST_PRACTICES_TOOLS array, including name, detailed description, and inputSchema with guideName enum listing all available guides.
{ name: 'get_best_practice_guide', description: 'Get a complete best practice and how-to guide with all sections and content. Use this when implementing specific SFCC features like cartridges, ISML templates, OCAPI/SCAPI hooks, SFRA controllers, or custom endpoints. Always consult the relevant guide before writing code to ensure you follow SFCC best practices, security guidelines, and proper architecture patterns.', inputSchema: { type: 'object', properties: { guideName: { type: 'string', enum: ['cartridge_creation', 'isml_templates', 'job_framework', 'localserviceregistry', 'ocapi_hooks', 'scapi_hooks', 'sfra_controllers', 'sfra_models', 'sfra_client_side_js', 'sfra_scss', 'scapi_custom_endpoint', 'performance', 'security'], description: 'The guide name (e.g., \'cartridge_creation\', \'isml_templates\', \'job_framework\', \'localserviceregistry\', \'ocapi_hooks\', \'scapi_hooks\', \'sfra_controllers\', \'sfra_models\', \'sfra_client_side_js\', \'sfra_scss\', \'scapi_custom_endpoint\', \'performance\', \'security\')', }, }, required: ['guideName'], }, }, - src/core/handlers/best-practices-handler.ts:40-42 (registration)Registration of the tool configs in the BestPracticesToolHandler class's getToolConfig method, which returns the BEST_PRACTICES_TOOL_CONFIG containing the handler for get_best_practice_guide.
protected getToolConfig(): Record<string, GenericToolSpec<ToolArguments, any>> { return BEST_PRACTICES_TOOL_CONFIG; } - src/core/server.ts:119-119 (registration)MCP server registration where BEST_PRACTICES_TOOLS (including get_best_practice_guide schema) is added to the list of available tools in ListToolsRequestHandler.
tools.push(...BEST_PRACTICES_TOOLS); - The core helper function in SFCCBestPracticesClient that implements the logic to securely read, parse, and structure the best practice guide markdown file, including caching, path traversal protection, content validation, title/section/description extraction, and error handling.
async getBestPracticeGuide(guideName: string): Promise<BestPracticeGuide | null> { const cacheKey = `best-practices:guide:${guideName}`; const cached = this.cache.getFileContent(cacheKey); if (cached) {return JSON.parse(cached);} try { // Enhanced security validation - validate guideName before path construction if (!guideName || typeof guideName !== 'string') { throw new Error('Invalid guide name: must be a non-empty string'); } // Prevent null bytes and dangerous characters in the guide name itself if (guideName.includes('\0') || guideName.includes('\x00')) { throw new Error('Invalid guide name: contains null bytes'); } // Prevent path traversal sequences in the guide name if (guideName.includes('..') || guideName.includes('/') || guideName.includes('\\')) { throw new Error('Invalid guide name: contains path traversal sequences'); } // Only allow alphanumeric characters, underscores, and hyphens if (!/^[a-zA-Z0-9_-]+$/.test(guideName)) { throw new Error('Invalid guide name: contains invalid characters'); } const filePath = path.join(this.docsPath, `${guideName}.md`); // Additional security validation - ensure the resolved path is within the docs directory const resolvedPath = path.resolve(filePath); const resolvedDocsPath = path.resolve(this.docsPath); if (!resolvedPath.startsWith(resolvedDocsPath)) { throw new Error('Invalid guide name: path outside allowed directory'); } // Ensure the file still ends with .md after path resolution if (!resolvedPath.toLowerCase().endsWith('.md')) { throw new Error('Invalid guide name: must reference a markdown file'); } const content = await fs.readFile(resolvedPath, 'utf-8'); // Basic content validation if (!content.trim()) { throw new Error(`Empty best practice guide: ${guideName}`); } // Check for binary content if (content.includes('\0')) { throw new Error(`Invalid content in best practice guide: ${guideName}`); } const lines = content.split('\n'); const title = lines.find(line => line.startsWith('#'))?.replace('#', '').trim() ?? guideName; // Extract sections (## headers) const sections = lines .filter(line => line.startsWith('##')) .map(line => line.replace('##', '').trim()); // Extract description (first paragraph after title) const descriptionStart = lines.findIndex(line => line.startsWith('#')) + 1; const descriptionEnd = lines.findIndex((line, index) => index > descriptionStart && (line.startsWith('#') || line.trim() === '')); const description = lines .slice(descriptionStart, descriptionEnd > -1 ? descriptionEnd : descriptionStart + 3) .join(' ') .trim(); const guide: BestPracticeGuide = { title, description, sections, content, }; this.cache.setFileContent(cacheKey, JSON.stringify(guide)); return guide; } catch (error) { this.logger.error(`Error reading best practice guide ${guideName}:`, error); return null; } }