@redpanda-data/docs-extensions-and-macros 4.13.6 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools-mcp.js +84 -131
- package/bin/doc-tools.js +105 -192
- package/bin/mcp-tools/generated-docs-review.js +11 -158
- package/bin/mcp-tools/index.js +0 -4
- package/bin/mcp-tools/mcp-validation.js +44 -149
- package/mcp/COSTS.adoc +7 -17
- package/mcp/DEVELOPMENT.adoc +2 -175
- package/mcp/README.adoc +27 -61
- package/mcp/WRITER_EXTENSION_GUIDE.adoc +128 -738
- package/package.json +1 -1
- package/bin/mcp-tools/content-review.js +0 -196
- package/bin/mcp-tools/frontmatter.js +0 -138
- package/bin/mcp-tools/prompt-discovery.js +0 -283
- package/mcp/prompts/README.adoc +0 -183
- package/mcp/prompts/property-docs-guide.md +0 -283
- package/mcp/prompts/review-for-style.md +0 -128
- package/mcp/prompts/rpcn-connector-docs-guide.md +0 -126
- package/mcp/prompts/write-new-guide.md +0 -222
- package/mcp/team-standards/style-guide.md +0 -321
- package/mcp/templates/README.adoc +0 -212
- package/mcp/templates/prompt-review-template.md +0 -80
- package/mcp/templates/prompt-write-template.md +0 -110
- package/mcp/templates/resource-template.md +0 -76
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Tools - Documentation Review
|
|
2
|
+
* MCP Tools - Generated Documentation Review
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Programmatic validation for auto-generated documentation.
|
|
5
|
+
* Checks for: missing descriptions, invalid xrefs, DRY violations, quality scoring.
|
|
6
|
+
*
|
|
7
|
+
* Note: Style and tone review is handled by the style-guide skill in docs-team-standards.
|
|
8
|
+
* This tool focuses on structural and technical validation only.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
const fs = require('fs');
|
|
11
12
|
const path = require('path');
|
|
12
13
|
const { findRepoRoot, formatDate } = require('./utils');
|
|
13
|
-
const cache = require('./cache');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Sanitize version string to prevent path traversal
|
|
@@ -53,54 +53,6 @@ function sanitizeVersion(version) {
|
|
|
53
53
|
return sanitized;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
* Build style review instructions for LLM-based review
|
|
58
|
-
* @param {string} docType - Type of documentation being reviewed
|
|
59
|
-
* @returns {string} Review instructions
|
|
60
|
-
*/
|
|
61
|
-
function buildStyleReviewInstructions(docType) {
|
|
62
|
-
const baseInstructions = `
|
|
63
|
-
# Style Guide Review Instructions
|
|
64
|
-
|
|
65
|
-
You are reviewing autogenerated ${docType} documentation for Redpanda.
|
|
66
|
-
|
|
67
|
-
## Your Task
|
|
68
|
-
|
|
69
|
-
Review the content sample below against the style guide and identify:
|
|
70
|
-
|
|
71
|
-
1. **Terminology issues**
|
|
72
|
-
- Incorrect capitalization (e.g., "RedPanda" should be "Redpanda")
|
|
73
|
-
- Deprecated terms (e.g., "whitelist/blacklist" should be "allowlist/denylist")
|
|
74
|
-
- Incorrect technical terms
|
|
75
|
-
|
|
76
|
-
2. **Voice and tone issues**
|
|
77
|
-
- Passive voice (should be active)
|
|
78
|
-
- Wrong tense (should be present tense)
|
|
79
|
-
- Wrong person (should be second person "you")
|
|
80
|
-
|
|
81
|
-
3. **Formatting issues**
|
|
82
|
-
- Heading capitalization (should be sentence case, not title case)
|
|
83
|
-
- Overuse of bold
|
|
84
|
-
- Use of em dashes (should use parentheses or colons instead)
|
|
85
|
-
|
|
86
|
-
4. **Structural issues**
|
|
87
|
-
- Incorrect heading hierarchy
|
|
88
|
-
- Missing descriptions or context
|
|
89
|
-
- Unclear explanations
|
|
90
|
-
|
|
91
|
-
## Output Format
|
|
92
|
-
|
|
93
|
-
For each issue found, provide:
|
|
94
|
-
- Line number or section reference
|
|
95
|
-
- Issue type (terminology/voice/formatting/structure)
|
|
96
|
-
- Specific problem
|
|
97
|
-
- Suggested fix
|
|
98
|
-
|
|
99
|
-
If no issues are found in a category, state "No issues found."
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
return baseInstructions;
|
|
103
|
-
}
|
|
104
56
|
|
|
105
57
|
/**
|
|
106
58
|
* Generate an AsciiDoc report from review results
|
|
@@ -337,22 +289,6 @@ function reviewGeneratedDocs(args) {
|
|
|
337
289
|
const repoRoot = findRepoRoot();
|
|
338
290
|
const { doc_type, version, generate_report } = args;
|
|
339
291
|
|
|
340
|
-
// Load style guide from cache or file (cache for 1 hour)
|
|
341
|
-
const styleGuidePath = path.join(repoRoot.root, 'mcp', 'team-standards', 'style-guide.md');
|
|
342
|
-
let styleGuide = null;
|
|
343
|
-
|
|
344
|
-
const cacheKey = 'style-guide-content';
|
|
345
|
-
styleGuide = cache.get(cacheKey);
|
|
346
|
-
|
|
347
|
-
if (!styleGuide && fs.existsSync(styleGuidePath)) {
|
|
348
|
-
try {
|
|
349
|
-
styleGuide = fs.readFileSync(styleGuidePath, 'utf8');
|
|
350
|
-
cache.set(cacheKey, styleGuide, 60 * 60 * 1000); // Cache for 1 hour
|
|
351
|
-
} catch (err) {
|
|
352
|
-
console.error(`Warning: Could not load style guide: ${err.message}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
292
|
if (!doc_type) {
|
|
357
293
|
return {
|
|
358
294
|
success: false,
|
|
@@ -365,9 +301,6 @@ function reviewGeneratedDocs(args) {
|
|
|
365
301
|
const suggestions = [];
|
|
366
302
|
let filesAnalyzed = 0;
|
|
367
303
|
let qualityScore = 100;
|
|
368
|
-
let contentSample = ''; // For storing content samples for LLM review
|
|
369
|
-
let contentFilePath = null; // Path to full content file for deep review if needed
|
|
370
|
-
let contentTotalLines = 0; // Total lines in the content file
|
|
371
304
|
|
|
372
305
|
try {
|
|
373
306
|
switch (doc_type) {
|
|
@@ -587,19 +520,6 @@ function reviewGeneratedDocs(args) {
|
|
|
587
520
|
qualityScore -= Math.min(5, Math.floor(propertiesNeedingExamples.length / 5));
|
|
588
521
|
}
|
|
589
522
|
|
|
590
|
-
// Sample generated property docs for style review (hybrid approach)
|
|
591
|
-
const clusterPropsPath = path.join(repoRoot.root, propertiesBaseDir, 'partials', 'properties', 'cluster-properties.adoc');
|
|
592
|
-
if (fs.existsSync(clusterPropsPath)) {
|
|
593
|
-
const fullContent = fs.readFileSync(clusterPropsPath, 'utf8');
|
|
594
|
-
const lines = fullContent.split('\n');
|
|
595
|
-
// Sample first 300 lines to catch systematic issues
|
|
596
|
-
// (enough to see multiple properties and their patterns)
|
|
597
|
-
contentSample = lines.slice(0, 300).join('\n');
|
|
598
|
-
// Store file path for full review if needed
|
|
599
|
-
contentFilePath = clusterPropsPath;
|
|
600
|
-
contentTotalLines = lines.length;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
523
|
break;
|
|
604
524
|
}
|
|
605
525
|
|
|
@@ -699,29 +619,12 @@ function reviewGeneratedDocs(args) {
|
|
|
699
619
|
suggestions.push('All $ref references are valid and DRY principles are maintained');
|
|
700
620
|
}
|
|
701
621
|
|
|
702
|
-
// Sample connector descriptions for style review
|
|
703
|
-
// Collect sample descriptions from inputs/outputs
|
|
704
|
-
const sampleDescriptions = [];
|
|
705
|
-
['inputs', 'outputs'].forEach(section => {
|
|
706
|
-
if (overrides[section]) {
|
|
707
|
-
Object.entries(overrides[section]).slice(0, 5).forEach(([name, config]) => {
|
|
708
|
-
if (config.description) {
|
|
709
|
-
sampleDescriptions.push(`\n== ${name} (${section})\n\n${config.description}`);
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
if (sampleDescriptions.length > 0) {
|
|
716
|
-
contentSample = `= RPCN Connector Descriptions Sample\n${sampleDescriptions.join('\n\n')}`;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
622
|
break;
|
|
720
623
|
}
|
|
721
624
|
|
|
722
625
|
case 'metrics':
|
|
723
626
|
case 'rpk': {
|
|
724
|
-
// For metrics and RPK, check files exist
|
|
627
|
+
// For metrics and RPK, check files exist
|
|
725
628
|
if (!version) {
|
|
726
629
|
return {
|
|
727
630
|
success: false,
|
|
@@ -740,11 +643,9 @@ function reviewGeneratedDocs(args) {
|
|
|
740
643
|
};
|
|
741
644
|
}
|
|
742
645
|
|
|
743
|
-
let filePath;
|
|
744
|
-
|
|
745
646
|
if (doc_type === 'metrics') {
|
|
746
647
|
// Use custom path or default
|
|
747
|
-
filePath = args.metrics_file ?
|
|
648
|
+
const filePath = args.metrics_file ?
|
|
748
649
|
path.join(repoRoot.root, args.metrics_file) :
|
|
749
650
|
path.join(repoRoot.root, 'modules', 'reference', 'pages', 'public-metrics-reference.adoc');
|
|
750
651
|
|
|
@@ -755,14 +656,6 @@ function reviewGeneratedDocs(args) {
|
|
|
755
656
|
suggestion: 'Generate metrics docs first using generate_metrics_docs tool, or specify custom path with metrics_file parameter'
|
|
756
657
|
};
|
|
757
658
|
}
|
|
758
|
-
|
|
759
|
-
// Read first 300 lines as sample for style review (hybrid approach)
|
|
760
|
-
const fullContent = fs.readFileSync(filePath, 'utf8');
|
|
761
|
-
const lines = fullContent.split('\n');
|
|
762
|
-
contentSample = lines.slice(0, 300).join('\n');
|
|
763
|
-
contentFilePath = filePath;
|
|
764
|
-
contentTotalLines = lines.length;
|
|
765
|
-
|
|
766
659
|
} else {
|
|
767
660
|
// RPK files are version-specific
|
|
768
661
|
const normalizedVersion = sanitizedVersion.startsWith('v') ? sanitizedVersion : `v${sanitizedVersion}`;
|
|
@@ -778,22 +671,6 @@ function reviewGeneratedDocs(args) {
|
|
|
778
671
|
suggestion: 'Generate RPK docs first using generate_rpk_docs tool, or specify custom path with rpk_dir parameter'
|
|
779
672
|
};
|
|
780
673
|
}
|
|
781
|
-
filePath = rpkDir;
|
|
782
|
-
|
|
783
|
-
// Sample 3 command files for review (hybrid approach)
|
|
784
|
-
const rpkFiles = fs.readdirSync(rpkDir).filter(f => f.endsWith('.adoc'));
|
|
785
|
-
if (rpkFiles.length > 0) {
|
|
786
|
-
const samplesToRead = Math.min(3, rpkFiles.length);
|
|
787
|
-
const samples = [];
|
|
788
|
-
for (let i = 0; i < samplesToRead; i++) {
|
|
789
|
-
const sampleFile = path.join(rpkDir, rpkFiles[i]);
|
|
790
|
-
const content = fs.readFileSync(sampleFile, 'utf8');
|
|
791
|
-
samples.push(`\n// File: ${rpkFiles[i]}\n${content}`);
|
|
792
|
-
}
|
|
793
|
-
contentSample = samples.join('\n\n---\n\n');
|
|
794
|
-
contentFilePath = rpkDir;
|
|
795
|
-
contentTotalLines = rpkFiles.length; // Total number of command files
|
|
796
|
-
}
|
|
797
674
|
}
|
|
798
675
|
|
|
799
676
|
filesAnalyzed++;
|
|
@@ -822,33 +699,9 @@ function reviewGeneratedDocs(args) {
|
|
|
822
699
|
quality_score: qualityScore,
|
|
823
700
|
suggestions,
|
|
824
701
|
summary: `Reviewed ${doc_type} documentation. Quality score: ${qualityScore}/100. Found ${issues.length} issues.`,
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
content_sample: contentSample || null,
|
|
829
|
-
content_file_path: contentFilePath,
|
|
830
|
-
content_total_lines: contentTotalLines || null,
|
|
831
|
-
content_sample_info: contentSample ?
|
|
832
|
-
(doc_type === 'rpk' ?
|
|
833
|
-
`Sample includes ${Math.min(3, contentTotalLines)} of ${contentTotalLines} command files` :
|
|
834
|
-
`Sample includes first 300 of ${contentTotalLines} lines`) : null,
|
|
835
|
-
// Instructions for LLM-based review
|
|
836
|
-
review_instructions: styleGuide && contentSample ? buildStyleReviewInstructions(doc_type) : null,
|
|
837
|
-
additional_review_needed: styleGuide && contentSample ?
|
|
838
|
-
'IMPORTANT: HYBRID REVIEW APPROACH\n\n' +
|
|
839
|
-
'1. First, review the content sample below against the style guide:\n' +
|
|
840
|
-
' - Terminology compliance (correct capitalization, approved terms)\n' +
|
|
841
|
-
' - Voice and tone (active voice, present tense, second person)\n' +
|
|
842
|
-
' - Formatting (heading capitalization, bold usage, em dashes)\n' +
|
|
843
|
-
' - Clarity and readability\n\n' +
|
|
844
|
-
'2. If you find style issues in the sample:\n' +
|
|
845
|
-
' - Report the issues found in the sample\n' +
|
|
846
|
-
' - Use the Read tool on content_file_path to review more content\n' +
|
|
847
|
-
' - Look for patterns that indicate systematic problems\n\n' +
|
|
848
|
-
'3. If no issues found in sample, the docs are likely good throughout.' :
|
|
849
|
-
styleGuide ?
|
|
850
|
-
'IMPORTANT: The programmatic review above checks for technical quality issues (missing descriptions, invalid xrefs, etc.). You should ALSO review the generated content against the style guide below for terminology, tone, and formatting compliance.' :
|
|
851
|
-
'Note: Style guide not available for additional review.'
|
|
702
|
+
// Note: Style review is handled by the style-guide skill in docs-team-standards
|
|
703
|
+
next_steps: 'This tool validates structural issues (missing descriptions, invalid refs, DRY violations). ' +
|
|
704
|
+
'For style and tone review, use the content-reviewer agent from docs-team-standards plugin.'
|
|
852
705
|
};
|
|
853
706
|
|
|
854
707
|
// Generate AsciiDoc report if requested
|
package/bin/mcp-tools/index.js
CHANGED
|
@@ -25,7 +25,6 @@ const { generateBundleOpenApi } = require('./openapi');
|
|
|
25
25
|
|
|
26
26
|
// Review tools
|
|
27
27
|
const { reviewGeneratedDocs, generateReviewReport } = require('./generated-docs-review');
|
|
28
|
-
const { reviewContent } = require('./content-review');
|
|
29
28
|
|
|
30
29
|
// Job queue
|
|
31
30
|
const { initializeJobQueue, createJob, getJob, listJobs, cleanupOldJobs } = require('./job-queue');
|
|
@@ -77,9 +76,6 @@ function executeTool(toolName, args = {}) {
|
|
|
77
76
|
case 'review_generated_docs':
|
|
78
77
|
return reviewGeneratedDocs(args);
|
|
79
78
|
|
|
80
|
-
case 'review_content':
|
|
81
|
-
return reviewContent(args, repoRoot.root);
|
|
82
|
-
|
|
83
79
|
case 'run_doc_tools_command': {
|
|
84
80
|
// Validate and execute raw doc-tools command
|
|
85
81
|
if (!args || typeof args !== 'object') {
|
|
@@ -1,171 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Configuration Validation
|
|
3
3
|
*
|
|
4
|
-
* Validates
|
|
5
|
-
*
|
|
4
|
+
* Validates MCP server configuration.
|
|
5
|
+
* Note: Prompts and most resources have been migrated to docs-team-standards plugin.
|
|
6
|
+
* This now only validates the personas resource and tools configuration.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
const fs = require('fs');
|
|
9
10
|
const path = require('path');
|
|
10
|
-
const { parsePromptFile } = require('./frontmatter');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {string} name - Prompt name to validate
|
|
15
|
-
* @throws {Error} If name is invalid
|
|
13
|
+
* Resources that are loaded dynamically (from current working directory)
|
|
16
14
|
*/
|
|
17
|
-
|
|
18
|
-
if (!name || typeof name !== 'string') {
|
|
19
|
-
throw new Error('Prompt name must be a non-empty string');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Only allow alphanumeric, hyphens, and underscores
|
|
23
|
-
if (!/^[a-z0-9_-]+$/i.test(name)) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Invalid prompt name: ${name}. Use only letters, numbers, hyphens, and underscores.`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return name;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Validate that required arguments are provided
|
|
34
|
-
* @param {string} promptName - Name of the prompt
|
|
35
|
-
* @param {Object} providedArgs - Arguments provided by user
|
|
36
|
-
* @param {Array} schema - Argument schema from prompt metadata
|
|
37
|
-
* @throws {Error} If validation fails
|
|
38
|
-
*/
|
|
39
|
-
function validatePromptArguments(promptName, providedArgs, schema) {
|
|
40
|
-
if (!schema || schema.length === 0) {
|
|
41
|
-
return; // No validation needed
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const errors = [];
|
|
45
|
-
|
|
46
|
-
// Check required arguments
|
|
47
|
-
schema
|
|
48
|
-
.filter(arg => arg.required)
|
|
49
|
-
.forEach(arg => {
|
|
50
|
-
if (!providedArgs || !providedArgs[arg.name]) {
|
|
51
|
-
errors.push(`Missing required argument: ${arg.name}`);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (errors.length > 0) {
|
|
56
|
-
throw new Error(
|
|
57
|
-
`Invalid arguments for prompt "${promptName}":\n${errors.join('\n')}`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
15
|
+
const DYNAMIC_RESOURCES = ['redpanda://personas'];
|
|
61
16
|
|
|
62
17
|
/**
|
|
63
|
-
* Validate
|
|
18
|
+
* Validate resources configuration
|
|
64
19
|
* @param {Array} resources - Resource definitions
|
|
65
|
-
* @param {Object} resourceMap - Resource file mappings
|
|
66
|
-
* @param {string} baseDir - Base directory for resources
|
|
67
20
|
* @returns {{ errors: string[], warnings: string[] }}
|
|
68
21
|
*/
|
|
69
|
-
function validateResources(resources
|
|
22
|
+
function validateResources(resources) {
|
|
70
23
|
const errors = [];
|
|
71
24
|
const warnings = [];
|
|
72
25
|
|
|
73
26
|
for (const resource of resources) {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
errors.push(`Resource ${resource.uri} has no file mapping in resourceMap`);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check file exists
|
|
82
|
-
const filePath = path.join(baseDir, 'mcp', 'team-standards', mapping.file);
|
|
83
|
-
if (!fs.existsSync(filePath)) {
|
|
84
|
-
errors.push(`Resource file missing: ${mapping.file} for ${resource.uri}`);
|
|
27
|
+
// All remaining resources should be dynamic (loaded from cwd)
|
|
28
|
+
if (!DYNAMIC_RESOURCES.includes(resource.uri)) {
|
|
29
|
+
warnings.push(`Unexpected resource: ${resource.uri} - static resources should be in docs-team-standards plugin`);
|
|
85
30
|
continue;
|
|
86
31
|
}
|
|
87
32
|
|
|
88
|
-
//
|
|
89
|
-
try {
|
|
90
|
-
fs.readFileSync(filePath, 'utf8');
|
|
91
|
-
} catch (err) {
|
|
92
|
-
errors.push(`Resource file not readable: ${mapping.file} - ${err.message}`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Validate resource metadata
|
|
33
|
+
// Validate metadata for dynamic resources
|
|
97
34
|
if (!resource.name || resource.name.length < 3) {
|
|
98
35
|
warnings.push(`Resource ${resource.uri} has a very short name`);
|
|
99
36
|
}
|
|
100
|
-
|
|
101
37
|
if (!resource.description || resource.description.length < 10) {
|
|
102
38
|
warnings.push(`Resource ${resource.uri} has a very short description`);
|
|
103
39
|
}
|
|
104
|
-
|
|
105
|
-
// Check for versioning
|
|
106
|
-
if (!resource.version) {
|
|
107
|
-
warnings.push(`Resource ${resource.uri} missing version metadata`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!resource.lastUpdated) {
|
|
111
|
-
warnings.push(`Resource ${resource.uri} missing lastUpdated metadata`);
|
|
112
|
-
}
|
|
113
40
|
}
|
|
114
41
|
|
|
115
42
|
return { errors, warnings };
|
|
116
43
|
}
|
|
117
44
|
|
|
118
45
|
/**
|
|
119
|
-
* Validate
|
|
120
|
-
* @param {Array}
|
|
46
|
+
* Validate tools configuration
|
|
47
|
+
* @param {Array} tools - Tool definitions
|
|
121
48
|
* @returns {{ errors: string[], warnings: string[] }}
|
|
122
49
|
*/
|
|
123
|
-
function
|
|
50
|
+
function validateTools(tools) {
|
|
124
51
|
const errors = [];
|
|
125
52
|
const warnings = [];
|
|
126
53
|
|
|
127
|
-
for (const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!prompt.content || prompt.content.trim().length < 100) {
|
|
134
|
-
warnings.push(`Prompt "${prompt.name}" has very little content (< 100 chars)`);
|
|
54
|
+
for (const tool of tools) {
|
|
55
|
+
if (!tool.name) {
|
|
56
|
+
errors.push('Tool missing name');
|
|
57
|
+
continue;
|
|
135
58
|
}
|
|
136
59
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
warnings.push(`Prompt "${prompt.name}" missing version metadata`);
|
|
60
|
+
if (!tool.description || tool.description.length < 10) {
|
|
61
|
+
warnings.push(`Tool "${tool.name}" has a very short description`);
|
|
140
62
|
}
|
|
141
63
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const validFormats = ['content-append', 'structured'];
|
|
145
|
-
if (!validFormats.includes(prompt.argumentFormat)) {
|
|
146
|
-
errors.push(
|
|
147
|
-
`Prompt "${prompt.name}" has invalid argumentFormat: ${prompt.argumentFormat}`
|
|
148
|
-
);
|
|
149
|
-
}
|
|
64
|
+
if (!tool.inputSchema) {
|
|
65
|
+
warnings.push(`Tool "${tool.name}" missing inputSchema`);
|
|
150
66
|
}
|
|
67
|
+
}
|
|
151
68
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Check for duplicate argument names
|
|
161
|
-
const argNames = new Set();
|
|
162
|
-
for (const arg of prompt.arguments) {
|
|
163
|
-
if (argNames.has(arg.name)) {
|
|
164
|
-
errors.push(`Prompt "${prompt.name}" has duplicate argument: ${arg.name}`);
|
|
165
|
-
}
|
|
166
|
-
argNames.add(arg.name);
|
|
167
|
-
}
|
|
69
|
+
// Check for duplicate names
|
|
70
|
+
const toolNames = new Set();
|
|
71
|
+
for (const tool of tools) {
|
|
72
|
+
if (tool.name && toolNames.has(tool.name)) {
|
|
73
|
+
errors.push(`Duplicate tool name: ${tool.name}`);
|
|
168
74
|
}
|
|
75
|
+
toolNames.add(tool.name);
|
|
169
76
|
}
|
|
170
77
|
|
|
171
78
|
return { errors, warnings };
|
|
@@ -175,9 +82,7 @@ function validatePrompts(prompts) {
|
|
|
175
82
|
* Validate entire MCP configuration
|
|
176
83
|
* @param {Object} config - Configuration object
|
|
177
84
|
* @param {Array} config.resources - Resources
|
|
178
|
-
* @param {
|
|
179
|
-
* @param {Array} config.prompts - Prompts
|
|
180
|
-
* @param {string} config.baseDir - Base directory
|
|
85
|
+
* @param {Array} config.tools - Tools
|
|
181
86
|
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
182
87
|
*/
|
|
183
88
|
function validateMcpConfiguration(config) {
|
|
@@ -185,26 +90,17 @@ function validateMcpConfiguration(config) {
|
|
|
185
90
|
const allWarnings = [];
|
|
186
91
|
|
|
187
92
|
// Validate resources
|
|
188
|
-
|
|
189
|
-
config.resources
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
allErrors.push(...resourceValidation.errors);
|
|
194
|
-
allWarnings.push(...resourceValidation.warnings);
|
|
195
|
-
|
|
196
|
-
// Validate prompts
|
|
197
|
-
const promptValidation = validatePrompts(config.prompts);
|
|
198
|
-
allErrors.push(...promptValidation.errors);
|
|
199
|
-
allWarnings.push(...promptValidation.warnings);
|
|
93
|
+
if (config.resources) {
|
|
94
|
+
const resourceValidation = validateResources(config.resources);
|
|
95
|
+
allErrors.push(...resourceValidation.errors);
|
|
96
|
+
allWarnings.push(...resourceValidation.warnings);
|
|
97
|
+
}
|
|
200
98
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
promptNames.add(prompt.name);
|
|
99
|
+
// Validate tools
|
|
100
|
+
if (config.tools) {
|
|
101
|
+
const toolValidation = validateTools(config.tools);
|
|
102
|
+
allErrors.push(...toolValidation.errors);
|
|
103
|
+
allWarnings.push(...toolValidation.warnings);
|
|
208
104
|
}
|
|
209
105
|
|
|
210
106
|
return {
|
|
@@ -217,6 +113,7 @@ function validateMcpConfiguration(config) {
|
|
|
217
113
|
/**
|
|
218
114
|
* Format validation results for display
|
|
219
115
|
* @param {{ valid: boolean, errors: string[], warnings: string[] }} results
|
|
116
|
+
* @param {Object} config - Configuration object
|
|
220
117
|
* @returns {string} Formatted output
|
|
221
118
|
*/
|
|
222
119
|
function formatValidationResults(results, config) {
|
|
@@ -227,8 +124,8 @@ function formatValidationResults(results, config) {
|
|
|
227
124
|
lines.push('');
|
|
228
125
|
|
|
229
126
|
// Summary
|
|
230
|
-
lines.push(`
|
|
231
|
-
lines.push(`Resources found: ${config.resources
|
|
127
|
+
lines.push(`Tools found: ${config.tools?.length || 0}`);
|
|
128
|
+
lines.push(`Resources found: ${config.resources?.length || 0}`);
|
|
232
129
|
lines.push('');
|
|
233
130
|
|
|
234
131
|
// Warnings
|
|
@@ -257,10 +154,8 @@ function formatValidationResults(results, config) {
|
|
|
257
154
|
}
|
|
258
155
|
|
|
259
156
|
module.exports = {
|
|
260
|
-
validatePromptName,
|
|
261
|
-
validatePromptArguments,
|
|
262
157
|
validateResources,
|
|
263
|
-
|
|
158
|
+
validateTools,
|
|
264
159
|
validateMcpConfiguration,
|
|
265
160
|
formatValidationResults
|
|
266
161
|
};
|
package/mcp/COSTS.adoc
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
= MCP Tool Costs
|
|
2
2
|
:description: Cost reference for Redpanda documentation MCP server tools
|
|
3
3
|
|
|
4
|
-
The typical cost range is $0.
|
|
4
|
+
The typical cost range is $0.03 - $0.19 per operation.
|
|
5
5
|
|
|
6
|
-
Most operations cost between $0.03 - $0.08.
|
|
6
|
+
Most operations cost between $0.03 - $0.08. Generated docs review costs up to $0.19 for comprehensive reviews.
|
|
7
7
|
|
|
8
8
|
== Cost factors
|
|
9
9
|
|
|
@@ -66,20 +66,10 @@ You control costs through:
|
|
|
66
66
|
|review_generated_docs
|
|
67
67
|
|Review the autogenerated metrics docs
|
|
68
68
|
|0.104 - 0.190
|
|
69
|
-
|
|
70
|
-
|review_content (terminology)
|
|
71
|
-
|Check terminology in a sentence
|
|
72
|
-
|0.017
|
|
73
|
-
|
|
74
|
-
|review_content (style)
|
|
75
|
-
|Review content for style guide compliance
|
|
76
|
-
|0.065
|
|
77
|
-
|
|
78
|
-
|review_content (comprehensive)
|
|
79
|
-
|Comprehensive content review
|
|
80
|
-
|0.062
|
|
81
69
|
|===
|
|
82
70
|
|
|
71
|
+
NOTE: The `review_content` tool was migrated to the `content-reviewer` agent in docs-team-standards. Content review now uses skills directly without MCP tool costs.
|
|
72
|
+
|
|
83
73
|
== How to measure costs
|
|
84
74
|
|
|
85
75
|
To track costs during testing:
|
|
@@ -162,6 +152,6 @@ claude --model opus
|
|
|
162
152
|
== Cost optimization tips
|
|
163
153
|
|
|
164
154
|
* *Choose the right model*: Use Haiku for simple generation tasks, Sonnet for most work
|
|
165
|
-
* *Leverage caching*:
|
|
166
|
-
* *Batch similar work*: Do multiple
|
|
167
|
-
* *Use
|
|
155
|
+
* *Leverage caching*: Repeated operations in the same session benefit from prompt caching
|
|
156
|
+
* *Batch similar work*: Do multiple generations in one session to maximize cache benefits
|
|
157
|
+
* *Use skills for review*: Content review now uses the docs-team-standards plugin (no MCP tool costs)
|