vcluster-yaml-mcp-server 1.0.7 → 1.0.9

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/src/server.js CHANGED
@@ -1,235 +1,7 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
- import yaml from 'js-yaml';
4
3
  import { githubClient } from './github.js';
5
- import { validateSnippet } from './snippet-validator.js';
6
-
7
- // Helper function to get the type of a value
8
- function getType(value) {
9
- if (value === null) return 'null';
10
- if (Array.isArray(value)) return 'array';
11
- if (typeof value === 'object') return 'object';
12
- if (typeof value === 'number') {
13
- return Number.isInteger(value) ? 'integer' : 'number';
14
- }
15
- return typeof value; // 'string', 'boolean'
16
- }
17
-
18
- // Helper function to format values for display
19
- function formatValue(value, path, indent = 0) {
20
- const spaces = ' '.repeat(indent);
21
-
22
- // Primitives (string, number, boolean, null)
23
- if (value === null) return 'null';
24
- if (typeof value !== 'object') return String(value);
25
-
26
- // Arrays
27
- if (Array.isArray(value)) {
28
- if (value.length === 0) return '[]';
29
- if (value.length <= 5 && value.every(v => typeof v !== 'object')) {
30
- // Small array of primitives - inline
31
- return JSON.stringify(value);
32
- }
33
- // Multi-line array
34
- return '\n' + value.map(v =>
35
- `${spaces} - ${formatValue(v, path, indent + 1)}`
36
- ).join('\n');
37
- }
38
-
39
- // Objects
40
- const keys = Object.keys(value);
41
- if (keys.length === 0) return '{}';
42
-
43
- // Small object (≤5 fields) - show all fields
44
- if (keys.length <= 5) {
45
- return '\n' + keys.map(key =>
46
- `${spaces} ${key}: ${formatValue(value[key], `${path}.${key}`, indent + 1)}`
47
- ).join('\n');
48
- }
49
-
50
- // Large object - show structure only
51
- return `\n${spaces} {object with ${keys.length} fields}`;
52
- }
53
-
54
- // Helper function to get field hints for common field names
55
- function getFieldHint(fieldName) {
56
- // Common field patterns and their hints
57
- const hints = {
58
- 'resources': 'Resource limits and requests',
59
- 'replicas': 'Number of replicas for HA',
60
- 'affinity': 'Pod affinity rules',
61
- 'tolerations': 'Pod toleration settings',
62
- 'nodeSelector': 'Node selection constraints',
63
- 'image': 'Container image configuration',
64
- 'enabled': 'Enable/disable this feature',
65
- 'annotations': 'Kubernetes annotations',
66
- 'labels': 'Kubernetes labels',
67
- 'ingress': 'Ingress configuration',
68
- 'service': 'Service configuration',
69
- 'storage': 'Storage configuration',
70
- 'persistence': 'Persistent volume settings',
71
- 'sync': 'Resource sync configuration',
72
- 'networking': 'Network settings'
73
- };
74
-
75
- return hints[fieldName] || '';
76
- }
77
-
78
- // Helper function to find related configs for a given item
79
- function findRelatedConfigs(item, allInfo) {
80
- const related = [];
81
- const pathParts = item.path.split('.');
82
- const lastKey = pathParts[pathParts.length - 1];
83
- const parentPath = pathParts.slice(0, -1).join('.');
84
-
85
- // Strategy 1: Find sibling fields (same parent path)
86
- const siblings = allInfo.filter(info => {
87
- const infoParent = info.path.split('.').slice(0, -1).join('.');
88
- return infoParent === parentPath &&
89
- info.path !== item.path &&
90
- typeof info.value === 'object' &&
91
- !Array.isArray(info.value);
92
- });
93
-
94
- // Add up to 2 sibling configs
95
- siblings.slice(0, 2).forEach(sibling => {
96
- const siblingKey = sibling.path.split('.').pop();
97
- related.push({
98
- path: sibling.path,
99
- hint: getFieldHint(siblingKey)
100
- });
101
- });
102
-
103
- // Strategy 2: Find same key name elsewhere (commonly configured together)
104
- if (related.length < 3) {
105
- const sameKeyElsewhere = allInfo.filter(info => {
106
- const infoKey = info.path.split('.').pop();
107
- return infoKey === lastKey &&
108
- info.path !== item.path &&
109
- !info.path.startsWith(item.path) && // Not a child
110
- typeof info.value === 'object' &&
111
- !Array.isArray(info.value);
112
- });
113
-
114
- // Add up to 1 same-key config from different section
115
- sameKeyElsewhere.slice(0, 1).forEach(same => {
116
- const section = same.path.split('.')[0];
117
- related.push({
118
- path: same.path,
119
- hint: `${lastKey} in ${section} section`
120
- });
121
- });
122
- }
123
-
124
- return related.slice(0, 3); // Max 3 related configs
125
- }
126
-
127
- // Helper function to format a single match result
128
- function formatMatch(item, index, total, allInfo) {
129
- const separator = '━'.repeat(60);
130
- let output = [];
131
-
132
- if (index > 0) output.push(''); // Blank line between matches
133
- output.push(separator);
134
- output.push('');
135
- output.push(`MATCH: ${item.path}`);
136
- output.push(`TYPE: ${getType(item.value)}`);
137
-
138
- // For primitives and simple values
139
- if (typeof item.value !== 'object' || item.value === null) {
140
- output.push(`VALUE: ${item.value}`);
141
- return output.join('\n');
142
- }
143
-
144
- // For arrays
145
- if (Array.isArray(item.value)) {
146
- if (item.value.length === 0) {
147
- output.push('VALUE: []');
148
- } else {
149
- output.push(`VALUE: ${formatValue(item.value, item.path)}`);
150
- }
151
- return output.join('\n');
152
- }
153
-
154
- // For objects - show fields
155
- const keys = Object.keys(item.value);
156
- output.push('');
157
-
158
- if (keys.length <= 10) {
159
- // Show all fields for small objects
160
- output.push('FIELDS:');
161
- keys.forEach(key => {
162
- const fieldValue = item.value[key];
163
- const fieldType = getType(fieldValue);
164
- output.push(` ${key} <${fieldType}>`);
165
- if (typeof fieldValue !== 'object' || fieldValue === null) {
166
- output.push(` value: ${fieldValue}`);
167
- } else if (Array.isArray(fieldValue)) {
168
- output.push(` value: [${fieldValue.length} items]`);
169
- } else {
170
- output.push(` value: {object with ${Object.keys(fieldValue).length} fields}`);
171
- }
172
- output.push('');
173
- });
174
- } else {
175
- // Show first 5 fields for large objects
176
- output.push(`FIELDS (${keys.length} total):`);
177
- keys.slice(0, 5).forEach(key => {
178
- const fieldType = getType(item.value[key]);
179
- output.push(` ${key} <${fieldType}>`);
180
- });
181
- output.push('');
182
- output.push(` ... ${keys.length - 5} more fields`);
183
- output.push('');
184
- output.push(`NOTE: Use query "${item.path}.fieldName" to see nested details`);
185
- }
186
-
187
- // Add related configs for objects
188
- if (typeof item.value === 'object' && !Array.isArray(item.value)) {
189
- const related = findRelatedConfigs(item, allInfo);
190
- if (related.length > 0) {
191
- output.push('');
192
- output.push('RELATED CONFIGS:');
193
- related.forEach(r => {
194
- output.push(` • ${r.path}${r.hint ? ' - ' + r.hint : ''}`);
195
- });
196
- }
197
- }
198
-
199
- return output.join('\n');
200
- }
201
-
202
- // Helper function to rank search results by relevance
203
- function rankResult(item, query) {
204
- let score = 0;
205
- const pathLower = item.path.toLowerCase();
206
- const keyLower = item.key.toLowerCase();
207
- const queryLower = query.toLowerCase();
208
-
209
- // Exact key match (highest priority)
210
- if (keyLower === queryLower) score += 100;
211
-
212
- // Exact path match
213
- if (pathLower === queryLower) score += 80;
214
-
215
- // Path ends with query
216
- if (pathLower.endsWith('.' + queryLower)) score += 50;
217
-
218
- // Key contains query
219
- if (keyLower.includes(queryLower)) score += 30;
220
-
221
- // Path contains query
222
- if (pathLower.includes(queryLower)) score += 10;
223
-
224
- // Prefer leaf values over objects
225
- if (item.isLeaf) score += 20;
226
-
227
- // Prefer shorter paths (less nesting = more relevant)
228
- const depth = item.path.split('.').length;
229
- score -= depth;
230
-
231
- return score;
232
- }
4
+ import { executeToolHandler } from './tool-registry.js';
233
5
 
234
6
  export function createServer() {
235
7
  const server = new Server(
@@ -353,521 +125,8 @@ export function createServer() {
353
125
  // Tool handler
354
126
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
355
127
  const { name, arguments: args } = request.params;
356
-
357
- try {
358
- switch (name) {
359
- case 'create-vcluster-config': {
360
- const { yaml_content, description, version } = args;
361
- const targetVersion = version || 'main';
362
-
363
- // Fetch schema for validation
364
- try {
365
- const schemaContent = await githubClient.getFileContent('chart/values.schema.json', targetVersion);
366
- const fullSchema = JSON.parse(schemaContent);
367
-
368
- // Validate the config
369
- const validationResult = validateSnippet(
370
- yaml_content,
371
- fullSchema,
372
- targetVersion,
373
- null // Auto-detect section
374
- );
375
-
376
- // Format response based on validation result
377
- let response = '';
378
-
379
- if (description) {
380
- response += `## ${description}\n\n`;
381
- }
382
-
383
- if (validationResult.valid) {
384
- response += `✅ **Configuration validated successfully!**\n\n`;
385
- response += `Version: ${targetVersion}\n`;
386
- if (validationResult.section) {
387
- response += `Section: ${validationResult.section}\n`;
388
- }
389
- response += `Validation time: ${validationResult.elapsed_ms}ms\n\n`;
390
- response += `### Configuration:\n\`\`\`yaml\n${yaml_content}\n\`\`\`\n`;
391
- } else {
392
- response += `❌ **Validation failed**\n\n`;
393
- if (validationResult.syntax_valid === false) {
394
- response += `**Syntax Error:**\n${validationResult.syntax_error}\n\n`;
395
- } else if (validationResult.errors && validationResult.errors.length > 0) {
396
- response += `**Validation Errors:**\n`;
397
- validationResult.errors.forEach((err, idx) => {
398
- response += `${idx + 1}. **${err.path}**: ${err.message}\n`;
399
- });
400
- response += `\n`;
401
- } else if (validationResult.error) {
402
- response += `**Error:** ${validationResult.error}\n\n`;
403
- if (validationResult.hint) {
404
- response += `**Hint:** ${validationResult.hint}\n\n`;
405
- }
406
- }
407
- response += `### Provided Configuration:\n\`\`\`yaml\n${yaml_content}\n\`\`\`\n`;
408
- }
409
-
410
- return {
411
- content: [
412
- {
413
- type: 'text',
414
- text: response
415
- }
416
- ],
417
- isError: !validationResult.valid
418
- };
419
- } catch (error) {
420
- return {
421
- content: [
422
- {
423
- type: 'text',
424
- text: `❌ **Failed to validate configuration**\n\nError: ${error.message}\n\n### Provided Configuration:\n\`\`\`yaml\n${yaml_content}\n\`\`\``
425
- }
426
- ],
427
- isError: true
428
- };
429
- }
430
- }
431
-
432
- case 'list-versions': {
433
- const tags = await githubClient.getTags();
434
-
435
- // Only show versions starting with 'v'
436
- const versionTags = tags.filter(tag => tag.startsWith('v'));
437
-
438
- // Always include main branch
439
- const versions = ['main', ...versionTags];
440
-
441
- return {
442
- content: [
443
- {
444
- type: 'text',
445
- text: `Available vCluster versions:\n\n${versions.slice(0, 20).map(v => `- ${v}`).join('\n')}\n${versions.length > 20 ? `... and ${versions.length - 20} more\n` : ''}`
446
- }
447
- ]
448
- };
449
- }
450
-
451
- case 'smart-query': {
452
- const version = args.version || 'main';
453
- const fileName = args.file || 'chart/values.yaml';
454
- let yamlData;
455
-
456
- try {
457
- yamlData = await githubClient.getYamlContent(fileName, version);
458
- } catch (error) {
459
- return {
460
- content: [
461
- {
462
- type: 'text',
463
- text: `Could not load ${fileName} from GitHub (version: ${version}). Error: ${error.message}\n\nTry:\n1. Check if the file exists in this version\n2. Try querying chart/values.yaml (default config file)\n3. Try a different version`
464
- }
465
- ]
466
- };
467
- }
468
-
469
- const searchTerm = args.query.toLowerCase();
470
- const results = [];
471
- const suggestions = new Set();
472
-
473
- // Helper function to extract all paths and values
474
- function extractInfo(obj, path = '') {
475
- const info = [];
476
- if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
477
- for (const [key, value] of Object.entries(obj)) {
478
- const currentPath = path ? `${path}.${key}` : key;
479
-
480
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
481
- info.push({ path: currentPath, key, value, isLeaf: false });
482
- info.push(...extractInfo(value, currentPath));
483
- } else {
484
- info.push({ path: currentPath, key, value, isLeaf: true });
485
- }
486
- }
487
- }
488
- return info;
489
- }
490
-
491
- const allInfo = extractInfo(yamlData);
492
-
493
- // Support dot notation queries (e.g., "controlPlane.ingress.enabled")
494
- const isDotNotation = searchTerm.includes('.');
495
-
496
- if (isDotNotation) {
497
- // Exact and partial dot notation matching
498
- for (const item of allInfo) {
499
- const pathLower = item.path.toLowerCase();
500
-
501
- // Exact match
502
- if (pathLower === searchTerm) {
503
- results.push(item);
504
- }
505
- // Ends with query (partial match)
506
- else if (pathLower.endsWith(searchTerm)) {
507
- results.push(item);
508
- }
509
- // Contains query
510
- else if (pathLower.includes(searchTerm)) {
511
- results.push(item);
512
- suggestions.add(item.path.split('.')[0]); // Suggest top-level
513
- }
514
- }
515
- } else {
516
- // Keyword-based search
517
- const keywords = searchTerm.split(/\s+/);
518
-
519
- for (const item of allInfo) {
520
- const pathLower = item.path.toLowerCase();
521
- const keyLower = item.key.toLowerCase();
522
- const valueStr = JSON.stringify(item.value).toLowerCase();
523
-
524
- // Check if ALL keywords match (AND logic for multi-word)
525
- const allKeywordsMatch = keywords.every(kw =>
526
- pathLower.includes(kw) || keyLower.includes(kw) || valueStr.includes(kw)
527
- );
528
-
529
- if (allKeywordsMatch) {
530
- results.push(item);
531
- suggestions.add(item.path.split('.')[0]);
532
- }
533
- }
534
- }
535
-
536
- // Sort results by relevance
537
- results.sort((a, b) => {
538
- const scoreA = rankResult(a, searchTerm);
539
- const scoreB = rankResult(b, searchTerm);
540
- return scoreB - scoreA; // Descending order
541
- });
542
-
543
- // Limit results to avoid token overflow
544
- const maxResults = 50;
545
- const limitedResults = results.slice(0, maxResults);
546
- const hasMore = results.length > maxResults;
547
-
548
- if (limitedResults.length === 0) {
549
- // Find similar paths
550
- const similarPaths = allInfo
551
- .filter(item => {
552
- const pathParts = item.path.toLowerCase().split('.');
553
- return pathParts.some(part => part.includes(searchTerm) || searchTerm.includes(part));
554
- })
555
- .slice(0, 5)
556
- .map(item => item.path);
557
-
558
- return {
559
- content: [
560
- {
561
- type: 'text',
562
- text: `No matches found for "${args.query}" in ${fileName} (${version}).\n\n` +
563
- (similarPaths.length > 0
564
- ? `Similar paths:\n${similarPaths.map(p => ` - ${p}`).join('\n')}\n\n`
565
- : '') +
566
- `Tips:\n` +
567
- ` - Use dot notation: "controlPlane.ingress.enabled"\n` +
568
- ` - Try broader terms: "${searchTerm.split('.')[0] || searchTerm.split(/\s+/)[0]}"\n` +
569
- ` - Use extract-validation-rules for section details\n\n` +
570
- `Top-level sections:\n${Object.keys(yamlData || {}).map(k => ` - ${k}`).join('\n')}`
571
- }
572
- ]
573
- };
574
- }
575
-
576
- // Format all results
577
- const formattedResults = limitedResults.map((item, idx) =>
578
- formatMatch(item, idx, limitedResults.length, allInfo)
579
- );
580
-
581
- return {
582
- content: [
583
- {
584
- type: 'text',
585
- text: `Found ${results.length} match${results.length === 1 ? '' : 'es'} for "${args.query}" in ${fileName} (${version})\n\n` +
586
- formattedResults.join('\n') +
587
- (hasMore ? `\n\n... showing ${maxResults} of ${results.length} total matches` : '')
588
- }
589
- ]
590
- };
591
- }
592
-
593
- case 'extract-validation-rules': {
594
- const version = args.version || 'main';
595
- const fileName = args.file || 'chart/values.yaml';
596
- let content;
597
-
598
- try {
599
- content = await githubClient.getFileContent(fileName, version);
600
- } catch (error) {
601
- return {
602
- content: [
603
- {
604
- type: 'text',
605
- text: `Error loading ${fileName}: ${error.message}`
606
- }
607
- ]
608
- };
609
- }
610
-
611
- const rules = extractValidationRulesFromComments(content, args.section);
612
-
613
- return {
614
- content: [
615
- {
616
- type: 'text',
617
- text: JSON.stringify(rules, null, 2)
618
- }
619
- ]
620
- };
621
- }
622
-
623
- case 'validate-config': {
624
- const version = args.version || 'main';
625
- let yamlContent;
626
-
627
- // Get YAML content
628
- try {
629
- if (args.content) {
630
- yamlContent = args.content;
631
- } else if (args.file) {
632
- yamlContent = await githubClient.getFileContent(args.file, version);
633
- } else {
634
- yamlContent = await githubClient.getFileContent('chart/values.yaml', version);
635
- }
636
- } catch (error) {
637
- return {
638
- content: [
639
- {
640
- type: 'text',
641
- text: JSON.stringify({
642
- valid: false,
643
- error: `Failed to load YAML: ${error.message}`
644
- }, null, 2)
645
- }
646
- ]
647
- };
648
- }
649
-
650
- // Fetch schema for validation
651
- try {
652
- const schemaContent = await githubClient.getFileContent('chart/values.schema.json', version);
653
- const fullSchema = JSON.parse(schemaContent);
654
-
655
- // Use snippet validator for validation
656
- const result = validateSnippet(
657
- yamlContent,
658
- fullSchema,
659
- version,
660
- null // Let it auto-detect section
661
- );
662
-
663
- return {
664
- content: [
665
- {
666
- type: 'text',
667
- text: JSON.stringify(result, null, 2)
668
- }
669
- ]
670
- };
671
- } catch (error) {
672
- return {
673
- content: [
674
- {
675
- type: 'text',
676
- text: JSON.stringify({
677
- valid: false,
678
- error: `Validation failed: ${error.message}`,
679
- version
680
- }, null, 2)
681
- }
682
- ]
683
- };
684
- }
685
- }
686
-
687
- default:
688
- throw new Error(`Unknown tool: ${name}`);
689
- }
690
- } catch (error) {
691
- return {
692
- content: [
693
- {
694
- type: 'text',
695
- text: `Error: ${error.message}`
696
- }
697
- ],
698
- isError: true
699
- };
700
- }
128
+ return executeToolHandler(name, args, githubClient);
701
129
  });
702
130
 
703
- // Extract validation rules from YAML comments for AI validation
704
- function extractValidationRulesFromComments(yamlContent, section) {
705
- const lines = yamlContent.split('\n');
706
- const rules = [];
707
- const enums = {};
708
- const dependencies = [];
709
- const defaults = {};
710
-
711
- let currentPath = [];
712
- let currentComments = [];
713
- let indentStack = [0];
714
-
715
- for (let i = 0; i < lines.length; i++) {
716
- const line = lines[i];
717
- const trimmedLine = line.trim();
718
-
719
- // Skip empty lines
720
- if (!trimmedLine) {
721
- currentComments = [];
722
- continue;
723
- }
724
-
725
- // Collect comments
726
- if (trimmedLine.startsWith('#')) {
727
- const comment = trimmedLine.substring(1).trim();
728
- if (comment && !comment.startsWith('#')) {
729
- currentComments.push(comment);
730
- }
731
- continue;
732
- }
733
-
734
- // Parse YAML structure
735
- const indent = line.search(/\S/);
736
- const keyMatch = line.match(/^(\s*)([a-zA-Z0-9_-]+):\s*(.*)?$/);
737
-
738
- if (keyMatch) {
739
- const key = keyMatch[2];
740
- const value = keyMatch[3];
741
-
742
- // Update path based on indentation
743
- while (indentStack.length > 1 && indent <= indentStack[indentStack.length - 1]) {
744
- indentStack.pop();
745
- currentPath.pop();
746
- }
747
-
748
- if (indent > indentStack[indentStack.length - 1]) {
749
- indentStack.push(indent);
750
- } else if (indent < indentStack[indentStack.length - 1]) {
751
- while (indentStack.length > 1 && indent < indentStack[indentStack.length - 1]) {
752
- indentStack.pop();
753
- currentPath.pop();
754
- }
755
- } else {
756
- currentPath.pop();
757
- }
758
-
759
- currentPath.push(key);
760
- const fullPath = currentPath.join('.');
761
-
762
- // Filter by section if specified
763
- if (section && !fullPath.startsWith(section)) {
764
- currentComments = [];
765
- continue;
766
- }
767
-
768
- // Extract validation instructions from comments
769
- if (currentComments.length > 0) {
770
- const instructions = [];
771
-
772
- for (const comment of currentComments) {
773
- // Extract enum values (e.g., "Valid values: a, b, c")
774
- const enumMatch = comment.match(/(?:valid values?|options?|choices?|possible values?):\s*(.+)/i);
775
- if (enumMatch) {
776
- const values = enumMatch[1].split(/[,;]/).map(v => v.trim()).filter(v => v);
777
- enums[fullPath] = values;
778
- instructions.push(`Valid values: ${values.join(', ')}`);
779
- }
780
-
781
- // Extract required dependencies
782
- if (comment.match(/requires?|depends on|needs?/i)) {
783
- dependencies.push(`${fullPath}: ${comment}`);
784
- instructions.push(comment);
785
- }
786
-
787
- // Extract defaults
788
- const defaultMatch = comment.match(/default(?:s)?\s*(?:is|:)?\s*(.+)/i);
789
- if (defaultMatch) {
790
- defaults[fullPath] = defaultMatch[1].trim();
791
- }
792
-
793
- // Extract validation rules
794
- if (comment.match(/must|should|cannot|only|at least|minimum|maximum|required/i)) {
795
- instructions.push(comment);
796
- }
797
-
798
- // Extract warnings
799
- if (comment.match(/warning|note|important|deprecated/i)) {
800
- instructions.push(`⚠️ ${comment}`);
801
- }
802
- }
803
-
804
- if (instructions.length > 0) {
805
- rules.push({
806
- path: fullPath,
807
- instructions: instructions,
808
- originalComments: currentComments
809
- });
810
- }
811
- }
812
-
813
- currentComments = [];
814
- }
815
- }
816
-
817
- // Generate AI validation instructions
818
- const aiInstructions = {
819
- summary: `Extracted ${rules.length} validation rules from YAML comments`,
820
- rules: rules,
821
- enums: enums,
822
- dependencies: dependencies,
823
- defaults: defaults,
824
- instructions: generateAiValidationInstructions(rules, enums, dependencies)
825
- };
826
-
827
- return aiInstructions;
828
- }
829
-
830
- function generateAiValidationInstructions(rules, enums, dependencies) {
831
- let instructions = '### AI Validation Instructions\n\n';
832
- instructions += 'Please validate the configuration using these rules extracted from comments:\n\n';
833
-
834
- if (rules.length > 0) {
835
- instructions += '#### Field-Specific Rules:\n';
836
- rules.forEach(rule => {
837
- instructions += `- **${rule.path}**:\n`;
838
- rule.instructions.forEach(inst => {
839
- instructions += ` - ${inst}\n`;
840
- });
841
- });
842
- instructions += '\n';
843
- }
844
-
845
- if (Object.keys(enums).length > 0) {
846
- instructions += '#### Enumeration Constraints:\n';
847
- instructions += 'Ensure these fields only contain the specified values:\n';
848
- Object.entries(enums).forEach(([field, values]) => {
849
- instructions += `- ${field}: [${values.join(', ')}]\n`;
850
- });
851
- instructions += '\n';
852
- }
853
-
854
- if (dependencies.length > 0) {
855
- instructions += '#### Dependencies to Check:\n';
856
- dependencies.forEach(dep => {
857
- instructions += `- ${dep}\n`;
858
- });
859
- instructions += '\n';
860
- }
861
-
862
- instructions += '#### Validation Approach:\n';
863
- instructions += '1. Check if all enumeration constraints are satisfied\n';
864
- instructions += '2. Verify all dependency requirements are met\n';
865
- instructions += '3. Validate against the specific rules for each field\n';
866
- instructions += '4. Flag any deprecated fields or configurations\n';
867
- instructions += '5. Provide helpful suggestions for fixing any issues found\n';
868
-
869
- return instructions;
870
- }
871
-
872
131
  return server;
873
132
  }