UNPKG

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

Version:

Antora extensions and macros developed for Redpanda documentation.

689 lines (651 loc) 23.8 kB
#!/usr/bin/env node /** * MCP Server for Redpanda Documentation Tools * * This server exposes domain-specific documentation tools to Claude Code * via the Model Context Protocol. * * Features: * - Context-aware: Works from any repository based on cwd * - Antora intelligence: Understands component/module structure * - Automation: Run doc-tools generate commands * - Auto-discovery: Prompts loaded automatically from mcp/prompts/ * - Validation: Startup checks ensure all resources are accessible * - Telemetry: Usage tracking for adoption metrics */ // Load environment variables from .env file if it exists require('dotenv').config(); const fs = require('fs'); const path = require('path'); const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } = require('@modelcontextprotocol/sdk/types.js'); const { findRepoRoot, executeTool } = require('./mcp-tools'); const { initializeJobQueue, getJob, listJobs } = require('./mcp-tools/job-queue'); // Prompts and most resources migrated to docs-team-standards plugin // This MCP server now focuses on execution tools only const { UsageStats, createPeriodicReporter, createShutdownHandler } = require('./mcp-tools/telemetry'); // Get version from package.json const packageJson = require('../package.json'); // Base directory const baseDir = path.join(__dirname, '..'); // Initialize usage stats (prompt cache removed - prompts migrated to docs-team-standards) const usageStats = new UsageStats(); // Create the MCP server const server = new Server( { name: 'redpanda-doc-tools-assistant', version: packageJson.version, }, { capabilities: { tools: {}, resources: {}, // prompts: {} - Removed, prompts migrated to docs-team-standards plugin }, } ); // Tool definitions - Writer-friendly documentation tools const tools = [ { name: 'get_antora_structure', description: 'Get information about the Antora documentation structure in the current repository, including components, modules, and available directories. Use this to understand the docs organization.', inputSchema: { type: 'object', properties: {}, required: [] } }, { name: 'get_redpanda_version', description: 'Get the latest Redpanda version information including version number, Docker tag, and release notes URL. Writers use this to find out what version to document.', inputSchema: { type: 'object', properties: { beta: { type: 'boolean', description: 'Whether to get the latest beta/RC version instead of stable (optional, defaults to false)' } }, required: [] } }, { name: 'get_console_version', description: 'Get the latest Redpanda Console version information including version number, Docker tag, and release notes URL.', inputSchema: { type: 'object', properties: {}, required: [] } }, { name: 'generate_property_docs', description: 'Generate Redpanda configuration property documentation for a specific version. Creates JSON and optionally AsciiDoc partials with all configuration properties. Writers use this when updating docs for a new Redpanda release. Automatically uses docs-data/property-overrides.json if present. Can run in background with progress streaming.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Git tag for released content (for example "25.3.1", "v25.3.1", or "latest"). Auto-prepends "v" if not present. Use tags for GA or beta releases.' }, branch: { type: 'string', description: 'Branch name for in-progress content (for example "dev", "main"). Use branches for documentation under development.' }, generate_partials: { type: 'boolean', description: 'Whether to generate AsciiDoc partials (cluster-properties.adoc, topic-properties.adoc, etc.). Default: false (only generates JSON)' }, overrides: { type: 'string', description: 'Path to optional JSON file with property description overrides (defaults to docs-data/property-overrides.json if present)' }, background: { type: 'boolean', description: 'Run as background job with progress updates. Returns job ID immediately instead of waiting. Default: false (run synchronously)' } }, required: [] } }, { name: 'generate_metrics_docs', description: 'Generate Redpanda metrics documentation for a specific version. Creates the public metrics reference page. Writers use this when updating metrics docs for a new release. Can run in background with progress streaming.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Git tag for released content (for example "25.3.1" or "v25.3.1"). Auto-prepends "v" if not present. Use tags for GA or beta releases.' }, branch: { type: 'string', description: 'Branch name for in-progress content (for example "dev", "main"). Use branches for documentation under development.' }, background: { type: 'boolean', description: 'Run as background job with progress updates. Returns job ID immediately instead of waiting. Default: false (run synchronously)' } }, required: [] } }, { name: 'generate_rpk_docs', description: 'Generate RPK command-line documentation for a specific version. Creates AsciiDoc files for all rpk commands. Writers use this when updating CLI docs for a new release. Can run in background with progress streaming.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Git tag for released content (for example "25.3.1" or "v25.3.1"). Auto-prepends "v" if not present. Use tags for GA or beta releases.' }, branch: { type: 'string', description: 'Branch name for in-progress content (for example "dev", "main"). Use branches for documentation under development.' }, background: { type: 'boolean', description: 'Run as background job with progress updates. Returns job ID immediately instead of waiting. Default: false (run synchronously)' } }, required: [] } }, { name: 'generate_rpcn_connector_docs', description: 'Generate Redpanda Connect connector documentation. Creates component documentation for all connectors. Writers use this when updating connector reference docs. Automatically uses docs-data/overrides.json if present.', inputSchema: { type: 'object', properties: { fetch_connectors: { type: 'boolean', description: 'Fetch latest connector data using rpk (optional, defaults to false)' }, draft_missing: { type: 'boolean', description: 'Generate full-doc drafts for connectors missing in output (optional, defaults to false)' }, update_whats_new: { type: 'boolean', description: 'Update whats-new.adoc with new section from diff JSON (optional, defaults to false)' }, include_bloblang: { type: 'boolean', description: 'Include Bloblang functions and methods in generation (optional, defaults to false)' }, data_dir: { type: 'string', description: 'Directory where versioned connect JSON files live (optional)' }, old_data: { type: 'string', description: 'Optional override for old data file (for diff)' }, csv: { type: 'string', description: 'Path to connector metadata CSV file (optional)' }, overrides: { type: 'string', description: 'Path to optional JSON file with overrides (defaults to docs-data/overrides.json if present)' }, cloud_version: { type: 'string', description: 'Specific cloud version to check (optional, auto-detects latest if not provided)' }, cgo_version: { type: 'string', description: 'Cgo binary version (optional, defaults to same as cloud-version)' } }, required: [] } }, { name: 'generate_helm_docs', description: 'Generate Helm chart documentation. Creates AsciiDoc documentation for Helm charts from local directories or GitHub repositories. Writers use this when updating Helm chart reference docs.', inputSchema: { type: 'object', properties: { chart_dir: { type: 'string', description: 'Chart directory (contains Chart.yaml) or root containing multiple charts, or a GitHub URL (optional, defaults to Redpanda operator charts)' }, tag: { type: 'string', description: 'Git tag for released content when using GitHub URL (for example "25.1.2" or "v25.1.2"). Auto-prepends "v" if not present. For redpanda-operator repository, also auto-prepends "operator/".' }, branch: { type: 'string', description: 'Branch name for in-progress content when using GitHub URL (for example "dev", "main").' }, readme: { type: 'string', description: 'Relative README.md path inside each chart dir (optional, defaults to "README.md")' }, output_dir: { type: 'string', description: 'Where to write all generated AsciiDoc files (optional, defaults to "modules/reference/pages")' }, output_suffix: { type: 'string', description: 'Suffix to append to each chart name including extension (optional, defaults to "-helm-spec.adoc")' } }, required: [] } }, { name: 'generate_cloud_regions', description: 'Generate cloud regions table documentation. Creates a Markdown or AsciiDoc table of cloud regions and tiers from GitHub YAML data. Writers use this when updating cloud region documentation.', inputSchema: { type: 'object', properties: { output: { type: 'string', description: 'Output file path relative to repo root (optional, defaults to "cloud-controlplane/x-topics/cloud-regions.md")' }, format: { type: 'string', description: 'Output format: "md" (Markdown) or "adoc" (AsciiDoc) (optional, defaults to "md")', enum: ['md', 'adoc'] }, owner: { type: 'string', description: 'GitHub repository owner (optional, defaults to "redpanda-data")' }, repo: { type: 'string', description: 'GitHub repository name (optional, defaults to "cloudv2-infra")' }, path: { type: 'string', description: 'Path to YAML file in repository (optional)' }, ref: { type: 'string', description: 'Git reference - branch, tag, or commit SHA (optional, defaults to "integration")' }, template: { type: 'string', description: 'Path to custom Handlebars template relative to repo root (optional)' }, dry_run: { type: 'boolean', description: 'Print output to stdout instead of writing file (optional, defaults to false)' } }, required: [] } }, { name: 'generate_crd_docs', description: 'Generate Kubernetes CRD (Custom Resource Definition) documentation. Creates AsciiDoc documentation for Kubernetes operator CRDs. Writers use this when updating operator reference docs.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Operator release tag (for example "operator/v25.1.2", "25.1.2", or "v25.1.2"). Auto-prepends "operator/" for redpanda-operator repository.' }, branch: { type: 'string', description: 'Branch name for in-progress content (for example "dev", "main").' }, source_path: { type: 'string', description: 'CRD Go types directory or GitHub URL (optional, defaults to Redpanda operator repo)' }, depth: { type: 'number', description: 'How many levels deep to generate (optional, defaults to 10)' }, templates_dir: { type: 'string', description: 'Asciidoctor templates directory (optional)' }, output: { type: 'string', description: 'Where to write the generated AsciiDoc file (optional, defaults to "modules/reference/pages/k-crd.adoc")' } }, required: [] } }, { name: 'generate_bundle_openapi', description: 'Bundle Redpanda OpenAPI fragments. Creates complete OpenAPI 3.1 documents for admin and/or connect APIs by bundling fragments from the Redpanda repository. Writers use this when updating API reference docs.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Git tag for released content (for example "v24.3.2" or "24.3.2"). Use tags for GA or beta releases.' }, branch: { type: 'string', description: 'Branch name for in-progress content (for example "dev", "main"). Use branches for documentation under development.' }, repo: { type: 'string', description: 'Repository URL (optional, defaults to Redpanda repo)' }, surface: { type: 'string', description: 'Which API surfaces to bundle (optional, defaults to "both")', enum: ['admin', 'connect', 'both'] }, out_admin: { type: 'string', description: 'Output path for admin API (optional, defaults to "admin/redpanda-admin-api.yaml")' }, out_connect: { type: 'string', description: 'Output path for connect API (optional, defaults to "connect/redpanda-connect-api.yaml")' }, admin_major: { type: 'string', description: 'Admin API major version (optional, defaults to "v2.0.0")' }, use_admin_major_version: { type: 'boolean', description: 'Use admin major version for info.version instead of git tag (optional, defaults to false)' }, quiet: { type: 'boolean', description: 'Suppress logs (optional, defaults to false)' } }, required: [] } }, { name: 'review_generated_docs', description: 'Validate auto-generated documentation for structural issues. Checks for missing descriptions, invalid $refs, DRY violations, invalid xrefs, and calculates quality scores. For style and tone review, use the content-reviewer agent from docs-team-standards.', inputSchema: { type: 'object', properties: { doc_type: { type: 'string', description: 'Type of documentation to review', enum: ['properties', 'metrics', 'rpk', 'rpcn_connectors'] }, version: { type: 'string', description: 'Version of the docs to review (required for properties, metrics, rpk; for example "25.3.1" or "v25.3.1")' }, generate_report: { type: 'boolean', description: 'Generate a formatted markdown report file for easy review (default: true)' } }, required: ['doc_type'] } }, { name: 'run_doc_tools_command', description: 'Advanced: Run a raw doc-tools command. Only use this if none of the specific tools above fit your needs. Requires knowledge of doc-tools CLI syntax.', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'The doc-tools command to run (without "npx doc-tools" prefix)' } }, required: ['command'] } }, { name: 'get_job_status', description: 'Get the status and progress of a background job. Use this to check on long-running documentation generation tasks.', inputSchema: { type: 'object', properties: { job_id: { type: 'string', description: 'Job ID returned when the job was created' } }, required: ['job_id'] } }, { name: 'list_jobs', description: 'List all background jobs with optional filtering. Use this to see recent documentation generation jobs and their status.', inputSchema: { type: 'object', properties: { status: { type: 'string', description: 'Filter by job status (optional)', enum: ['pending', 'running', 'completed', 'failed'] }, tool: { type: 'string', description: 'Filter by tool name (optional)' } }, required: [] } } ]; // Resource definitions - Only personas remains (other standards migrated to docs-team-standards plugin) const resources = [ { uri: 'redpanda://personas', name: 'Repository Personas', description: 'Target audience personas for this repository. Loaded from docs-data/personas.yaml in the current working directory. Defines reader types, goals, pain points, and content preferences.', mimeType: 'text/yaml', version: '1.0.0', lastUpdated: '2025-01-07' } ]; // Resource file mappings (most resources migrated to docs-team-standards plugin) const resourceMap = { // Only personas remains - it's loaded dynamically from cwd, not from team-standards/ }; /** * Load resource content from team-standards directory * @param {string} uri - Resource URI (such as 'redpanda://style-guide') * @returns {Object} Resource content */ function getResourceContent(uri) { // Special handling for personas - load from current working directory if (uri === 'redpanda://personas') { return getPersonasResource(); } const resource = resourceMap[uri]; if (!resource) { throw new Error(`Unknown resource: ${uri}`); } const resourcePath = path.join(baseDir, 'team-standards', resource.file); try { const content = fs.readFileSync(resourcePath, 'utf8'); return { contents: [{ uri, mimeType: resource.mimeType, text: content }] }; } catch (err) { console.error(`Error loading resource ${uri}: ${err.message}`); throw new Error(`Resource not found: ${uri}`); } } /** * Load personas from current repository's docs-data/personas.yaml * Falls back to a helpful message if not found * @returns {Object} Resource content */ function getPersonasResource() { const cwd = process.cwd(); const personasPath = path.join(cwd, 'docs-data', 'personas.yaml'); try { if (fs.existsSync(personasPath)) { const content = fs.readFileSync(personasPath, 'utf8'); return { contents: [{ uri: 'redpanda://personas', mimeType: 'text/yaml', text: content }] }; } else { // Return helpful message when no personas file exists const fallbackContent = `# No personas defined for this repository # # To define personas, create: docs-data/personas.yaml # # Example: # personas: # - id: developer # name: Application Developer # description: Builds applications using this product # goals: # - Integrate quickly # - Find working examples # pain_points: # - Complex configuration # - Missing examples # content_preferences: # - Code-first explanations # - Copy-paste examples # # For the full template, see: # https://github.com/redpanda-data/docs-extensions-and-macros/blob/main/team-standards/personas-template.yaml # # Current working directory: ${cwd} `; return { contents: [{ uri: 'redpanda://personas', mimeType: 'text/yaml', text: fallbackContent }] }; } } catch (err) { console.error(`Error loading personas: ${err.message}`); throw new Error(`Failed to load personas: ${err.message}`); } } // Handle list tools request server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Prompts handler removed - prompts migrated to docs-team-standards plugin // Handle list resources request server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources }; }); // Handle read resource request server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; // Record usage usageStats.recordResource(uri); return getResourceContent(uri); }); // Get prompt handler removed - prompts migrated to docs-team-standards plugin // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // Record usage usageStats.recordTool(name); // Handle job management tools if (name === 'get_job_status') { const job = getJob(args.job_id); if (!job) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: `Job not found: ${args.job_id}`, suggestion: 'Check the job ID or use list_jobs to see available jobs' }, null, 2) } ] }; } return { content: [ { type: 'text', text: JSON.stringify({ success: true, job }, null, 2) } ] }; } if (name === 'list_jobs') { const jobs = listJobs(args || {}); return { content: [ { type: 'text', text: JSON.stringify({ success: true, jobs, total: jobs.length }, null, 2) } ] }; } // Handle regular tools const result = executeTool(name, args || {}); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; }); // Start the server async function main() { // Prompts have been migrated to docs-team-standards plugin (skills/agents) // No prompts to load from this MCP server console.error('Prompts: migrated to docs-team-standards plugin'); // Initialize usage tracking createShutdownHandler(usageStats, baseDir); // Periodic reporting (every hour) if (process.env.MCP_TELEMETRY_REPORTING === 'true') { createPeriodicReporter(usageStats, 3600000); } // Connect MCP server const transport = new StdioServerTransport(); await server.connect(transport); // Initialize job queue with server instance for progress notifications initializeJobQueue(server); // Log to stderr so it doesn't interfere with MCP protocol on stdout const repoInfo = findRepoRoot(); console.error('Redpanda Doc Tools MCP Server running'); console.error(`Server version: ${packageJson.version}`); console.error(`Working directory: ${process.cwd()}`); console.error(`Repository root: ${repoInfo.root} (${repoInfo.detected ? repoInfo.type : 'not detected'})`); console.error('Mode: execution tools only (prompts/resources migrated to docs-team-standards)'); console.error('Background job queue: enabled'); console.error('Command timeout: 10 minutes'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); });