UNPKG

@redpanda-data/docs-extensions-and-macros

Version:

Antora extensions and macros developed for Redpanda documentation.

261 lines (220 loc) 6.97 kB
/** * MCP Tools - Main Exports * * This module exports all MCP tools in a modular structure. */ // Utilities const { findRepoRoot, executeCommand, normalizeVersion, formatDate, serializeResult } = require('./utils'); // Antora const { getAntoraStructure } = require('./antora'); // Versions const { getRedpandaVersion, getConsoleVersion } = require('./versions'); // Documentation generation tools const { generatePropertyDocs } = require('./property-docs'); const { generateMetricsDocs } = require('./metrics-docs'); const { generateRpkDocs } = require('./rpk-docs'); const { generateRpConnectDocs } = require('./rpcn-docs'); const { generateHelmDocs } = require('./helm-docs'); const { generateCloudRegions } = require('./cloud-regions'); const { generateCrdDocs } = require('./crd-docs'); const { generateBundleOpenApi } = require('./openapi'); // Review tools const { reviewGeneratedDocs, generateReviewReport } = require('./generated-docs-review'); // Job queue const { initializeJobQueue, createJob, getJob, listJobs, cleanupOldJobs } = require('./job-queue'); /** * Execute a tool and return results * @param {string} toolName - Name of the tool to execute * @param {Object} args - Arguments for the tool * @returns {Object} Tool execution results */ function executeTool(toolName, args = {}) { const repoRoot = findRepoRoot(); try { let result; switch (toolName) { case 'get_antora_structure': result = getAntoraStructure(repoRoot); break; case 'get_redpanda_version': result = getRedpandaVersion(args); break; case 'get_console_version': result = getConsoleVersion(); break; case 'generate_property_docs': result = generatePropertyDocs(args); break; case 'generate_metrics_docs': result = generateMetricsDocs(args); break; case 'generate_rpk_docs': result = generateRpkDocs(args); break; case 'generate_rpcn_connector_docs': result = generateRpConnectDocs(args); break; case 'generate_helm_docs': result = generateHelmDocs(args); break; case 'generate_cloud_regions': result = generateCloudRegions(args); break; case 'generate_crd_docs': result = generateCrdDocs(args); break; case 'generate_bundle_openapi': result = generateBundleOpenApi(args); break; case 'review_generated_docs': result = reviewGeneratedDocs(args); break; case 'run_doc_tools_command': { // Validate and execute raw doc-tools command if (!args || typeof args !== 'object') { result = { success: false, error: 'Invalid arguments: expected an object' }; break; } const validation = validateDocToolsCommand(args.command); if (!validation.valid) { result = { success: false, error: validation.error }; break; } try { // Get doc-tools command (handles both local and installed) const { getDocToolsCommand } = require('./utils'); const docTools = getDocToolsCommand(repoRoot); // Parse command into argument array (no shell interpolation) // Since validation already rejects shell metacharacters, we can safely split by spaces // Handle quoted strings for paths with spaces const cmdArgs = parseCommandArgs(args.command); // Build full args using the appropriate command const fullArgs = docTools.getArgs(cmdArgs); const output = executeCommand(docTools.program, fullArgs, { cwd: repoRoot.root }); result = { success: true, output: output.trim(), command: `${docTools.program} ${fullArgs.join(' ')}` }; } catch (err) { result = { success: false, error: err.message, stdout: err.stdout || '', stderr: err.stderr || '' }; } break; } default: result = { success: false, error: `Unknown tool: ${toolName}`, suggestion: 'Check the tool name and try again' }; } // Serialize the result to handle any Error objects that might have slipped through return serializeResult(result); } catch (err) { return serializeResult({ success: false, error: err.message, suggestion: 'An unexpected error occurred while executing the tool' }); } } /** * Parse command string into array of arguments * Handles quoted strings for paths with spaces * @param {string} command - The command string to parse * @returns {string[]} Array of arguments */ function parseCommandArgs(command) { const args = []; let current = ''; let inQuotes = false; let quoteChar = ''; for (let i = 0; i < command.length; i++) { const char = command[i]; if ((char === '"' || char === "'") && !inQuotes) { // Start of quoted string inQuotes = true; quoteChar = char; } else if (char === quoteChar && inQuotes) { // End of quoted string inQuotes = false; quoteChar = ''; } else if (char === ' ' && !inQuotes) { // Space outside quotes - end of argument if (current) { args.push(current); current = ''; } } else { // Regular character current += char; } } // Add final argument if present if (current) { args.push(current); } return args; } /** * Validate doc-tools command to prevent command injection * @param {string} command - The command string to validate * @returns {{ valid: boolean, error?: string }} Validation result */ function validateDocToolsCommand(command) { if (!command || typeof command !== 'string') { return { valid: false, error: 'Command must be a non-empty string' }; } const dangerousChars = /[;|&$`<>(){}[\]!*?~]/; if (dangerousChars.test(command)) { return { valid: false, error: 'Invalid command: shell metacharacters not allowed. Use simple doc-tools commands only.' }; } if (command.includes('..') || command.includes('~')) { return { valid: false, error: 'Invalid command: path traversal sequences not allowed' }; } return { valid: true }; } module.exports = { // Core functions findRepoRoot, executeTool, // Individual tool exports (for testing or direct use) getAntoraStructure, getRedpandaVersion, getConsoleVersion, generatePropertyDocs, generateMetricsDocs, generateRpkDocs, generateRpConnectDocs, generateHelmDocs, generateCloudRegions, generateCrdDocs, generateBundleOpenApi, reviewGeneratedDocs, generateReviewReport, // Job queue initializeJobQueue, createJob, getJob, listJobs, cleanupOldJobs };