#
tokens: 42976/50000 4/189 files (page 10/12)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 10 of 12. Use http://codebase.md/portel-dev/ncp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .dxtignore
├── .github
│   ├── FEATURE_STORY_TEMPLATE.md
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── mcp_server_request.yml
│   ├── pull_request_template.md
│   └── workflows
│       ├── ci.yml
│       ├── publish-mcp-registry.yml
│       └── release.yml
├── .gitignore
├── .mcpbignore
├── .npmignore
├── .release-it.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── COMPLETE-IMPLEMENTATION-SUMMARY.md
├── CONTRIBUTING.md
├── CRITICAL-ISSUES-FOUND.md
├── docs
│   ├── clients
│   │   ├── claude-desktop.md
│   │   ├── cline.md
│   │   ├── continue.md
│   │   ├── cursor.md
│   │   ├── perplexity.md
│   │   └── README.md
│   ├── download-stats.md
│   ├── guides
│   │   ├── clipboard-security-pattern.md
│   │   ├── how-it-works.md
│   │   ├── mcp-prompts-for-user-interaction.md
│   │   ├── mcpb-installation.md
│   │   ├── ncp-registry-command.md
│   │   ├── pre-release-checklist.md
│   │   ├── telemetry-design.md
│   │   └── testing.md
│   ├── images
│   │   ├── ncp-add.png
│   │   ├── ncp-find.png
│   │   ├── ncp-help.png
│   │   ├── ncp-import.png
│   │   ├── ncp-list.png
│   │   └── ncp-transformation-flow.png
│   ├── mcp-registry-setup.md
│   ├── pr-schema-additions.ts
│   └── stories
│       ├── 01-dream-and-discover.md
│       ├── 02-secrets-in-plain-sight.md
│       ├── 03-sync-and-forget.md
│       ├── 04-double-click-install.md
│       ├── 05-runtime-detective.md
│       └── 06-official-registry.md
├── DYNAMIC-RUNTIME-SUMMARY.md
├── EXTENSION-CONFIG-DISCOVERY.md
├── INSTALL-EXTENSION.md
├── INTERNAL-MCP-ARCHITECTURE.md
├── jest.config.js
├── LICENSE
├── MANAGEMENT-TOOLS-COMPLETE.md
├── manifest.json
├── manifest.json.backup
├── MCP-CONFIG-SCHEMA-IMPLEMENTATION-EXAMPLE.ts
├── MCP-CONFIG-SCHEMA-SIMPLE-EXAMPLE.json
├── MCP-CONFIGURATION-SCHEMA-FORMAT.json
├── MCPB-ARCHITECTURE-DECISION.md
├── NCP-EXTENSION-COMPLETE.md
├── package-lock.json
├── package.json
├── parity-between-cli-and-mcp.txt
├── PROMPTS-IMPLEMENTATION.md
├── README-COMPARISON.md
├── README.md
├── README.new.md
├── REGISTRY-INTEGRATION-COMPLETE.md
├── RELEASE-PROCESS-IMPROVEMENTS.md
├── RELEASE-SUMMARY.md
├── RELEASE.md
├── RUNTIME-DETECTION-COMPLETE.md
├── scripts
│   ├── cleanup
│   │   └── scan-repository.js
│   └── sync-server-version.cjs
├── SECURITY.md
├── server.json
├── src
│   ├── analytics
│   │   ├── analytics-formatter.ts
│   │   ├── log-parser.ts
│   │   └── visual-formatter.ts
│   ├── auth
│   │   ├── oauth-device-flow.ts
│   │   └── token-store.ts
│   ├── cache
│   │   ├── cache-patcher.ts
│   │   ├── csv-cache.ts
│   │   └── schema-cache.ts
│   ├── cli
│   │   └── index.ts
│   ├── discovery
│   │   ├── engine.ts
│   │   ├── mcp-domain-analyzer.ts
│   │   ├── rag-engine.ts
│   │   ├── search-enhancer.ts
│   │   └── semantic-enhancement-engine.ts
│   ├── extension
│   │   └── extension-init.ts
│   ├── index-mcp.ts
│   ├── index.ts
│   ├── internal-mcps
│   │   ├── internal-mcp-manager.ts
│   │   ├── ncp-management.ts
│   │   └── types.ts
│   ├── orchestrator
│   │   └── ncp-orchestrator.ts
│   ├── profiles
│   │   └── profile-manager.ts
│   ├── server
│   │   ├── mcp-prompts.ts
│   │   └── mcp-server.ts
│   ├── services
│   │   ├── config-prompter.ts
│   │   ├── config-schema-reader.ts
│   │   ├── error-handler.ts
│   │   ├── output-formatter.ts
│   │   ├── registry-client.ts
│   │   ├── tool-context-resolver.ts
│   │   ├── tool-finder.ts
│   │   ├── tool-schema-parser.ts
│   │   └── usage-tips-generator.ts
│   ├── testing
│   │   ├── create-real-mcp-definitions.ts
│   │   ├── dummy-mcp-server.ts
│   │   ├── mcp-definitions.json
│   │   ├── real-mcp-analyzer.ts
│   │   ├── real-mcp-definitions.json
│   │   ├── real-mcps.csv
│   │   ├── setup-dummy-mcps.ts
│   │   ├── setup-tiered-profiles.ts
│   │   ├── test-profile.json
│   │   ├── test-semantic-enhancement.ts
│   │   └── verify-profile-scaling.ts
│   ├── transports
│   │   └── filtered-stdio-transport.ts
│   └── utils
│       ├── claude-desktop-importer.ts
│       ├── client-importer.ts
│       ├── client-registry.ts
│       ├── config-manager.ts
│       ├── health-monitor.ts
│       ├── highlighting.ts
│       ├── logger.ts
│       ├── markdown-renderer.ts
│       ├── mcp-error-parser.ts
│       ├── mcp-wrapper.ts
│       ├── ncp-paths.ts
│       ├── parameter-prompter.ts
│       ├── paths.ts
│       ├── progress-spinner.ts
│       ├── response-formatter.ts
│       ├── runtime-detector.ts
│       ├── schema-examples.ts
│       ├── security.ts
│       ├── text-utils.ts
│       ├── update-checker.ts
│       ├── updater.ts
│       └── version.ts
├── STORY-DRIVEN-DOCUMENTATION.md
├── STORY-FIRST-WORKFLOW.md
├── test
│   ├── __mocks__
│   │   ├── chalk.js
│   │   ├── transformers.js
│   │   ├── updater.js
│   │   └── version.ts
│   ├── cache-loading-focused.test.ts
│   ├── cache-optimization.test.ts
│   ├── cli-help-validation.sh
│   ├── coverage-boost.test.ts
│   ├── curated-ecosystem-validation.test.ts
│   ├── discovery-engine.test.ts
│   ├── discovery-fallback-focused.test.ts
│   ├── ecosystem-discovery-focused.test.ts
│   ├── ecosystem-discovery-validation-simple.test.ts
│   ├── final-80-percent-push.test.ts
│   ├── final-coverage-push.test.ts
│   ├── health-integration.test.ts
│   ├── health-monitor.test.ts
│   ├── helpers
│   │   └── mock-server-manager.ts
│   ├── integration
│   │   └── mcp-client-simulation.test.cjs
│   ├── logger.test.ts
│   ├── mcp-ecosystem-discovery.test.ts
│   ├── mcp-error-parser.test.ts
│   ├── mcp-immediate-response-check.js
│   ├── mcp-server-protocol.test.ts
│   ├── mcp-timeout-scenarios.test.ts
│   ├── mcp-wrapper.test.ts
│   ├── mock-mcps
│   │   ├── aws-server.js
│   │   ├── base-mock-server.mjs
│   │   ├── brave-search-server.js
│   │   ├── docker-server.js
│   │   ├── filesystem-server.js
│   │   ├── git-server.mjs
│   │   ├── github-server.js
│   │   ├── neo4j-server.js
│   │   ├── notion-server.js
│   │   ├── playwright-server.js
│   │   ├── postgres-server.js
│   │   ├── shell-server.js
│   │   ├── slack-server.js
│   │   └── stripe-server.js
│   ├── mock-smithery-mcp
│   │   ├── index.js
│   │   ├── package.json
│   │   └── smithery.yaml
│   ├── ncp-orchestrator.test.ts
│   ├── orchestrator-health-integration.test.ts
│   ├── orchestrator-simple-branches.test.ts
│   ├── performance-benchmark.test.ts
│   ├── quick-coverage.test.ts
│   ├── rag-engine.test.ts
│   ├── regression-snapshot.test.ts
│   ├── search-enhancer.test.ts
│   ├── session-id-passthrough.test.ts
│   ├── setup.ts
│   ├── tool-context-resolver.test.ts
│   ├── tool-schema-parser.test.ts
│   ├── user-story-discovery.test.ts
│   └── version-util.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/src/utils/config-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { readFileSync, existsSync } from 'fs';
  2 | import { createInterface } from 'readline';
  3 | import chalk from 'chalk';
  4 | import clipboardy from 'clipboardy';
  5 | import { ProfileManager } from '../profiles/profile-manager.js';
  6 | import { OutputFormatter } from '../services/output-formatter.js';
  7 | import { ErrorHandler } from '../services/error-handler.js';
  8 | import { formatCommandDisplay } from '../utils/security.js';
  9 | import { TextUtils } from '../utils/text-utils.js';
 10 | import { logger } from '../utils/logger.js';
 11 | 
 12 | interface MCPConfig {
 13 |   command?: string;  // Optional: for stdio transport
 14 |   args?: string[];
 15 |   env?: Record<string, string>;
 16 |   url?: string;  // Optional: for HTTP/SSE transport
 17 | }
 18 | 
 19 | interface MCPImportData {
 20 |   [mcpName: string]: MCPConfig;
 21 | }
 22 | 
 23 | export class ConfigManager {
 24 |   private profileManager: ProfileManager;
 25 | 
 26 |   constructor() {
 27 |     this.profileManager = new ProfileManager();
 28 |   }
 29 | 
 30 |   /**
 31 |    * Show the location of NCP config files
 32 |    */
 33 |   async showConfigLocations(): Promise<void> {
 34 |     await this.profileManager.initialize();
 35 |     const configDir = this.profileManager.getConfigPath();
 36 | 
 37 |     console.log(chalk.blue('📁 NCP Configuration:'));
 38 |     console.log(`  Profiles Directory: ${configDir}`);
 39 | 
 40 |     if (existsSync(configDir)) {
 41 |       console.log(chalk.green('  ✓ Config directory exists'));
 42 | 
 43 |       // List existing profiles
 44 |       const profiles = this.profileManager.listProfiles();
 45 |       if (profiles.length > 0) {
 46 |         console.log(`  📋 Found ${profiles.length} profiles:`);
 47 |         profiles.forEach(profile => {
 48 |           const profilePath = this.profileManager.getProfilePath(profile);
 49 |           console.log(`    • ${profile}: ${profilePath}`);
 50 |         });
 51 |       } else {
 52 |         console.log(chalk.yellow('    No profiles created yet'));
 53 |       }
 54 |     } else {
 55 |       console.log(chalk.yellow('  ⚠ Config directory will be created on first use'));
 56 |     }
 57 |   }
 58 | 
 59 |   /**
 60 |    * Open existing config directory in default editor/explorer
 61 |    */
 62 |   async editConfig(): Promise<void> {
 63 |     await this.profileManager.initialize();
 64 |     const configDir = this.profileManager.getConfigPath();
 65 | 
 66 |     if (!existsSync(configDir)) {
 67 |       console.log(chalk.yellow('⚠ Config directory does not exist yet. Use "ncp config --import" to create it.'));
 68 |       return;
 69 |     }
 70 | 
 71 |     const profiles = this.profileManager.listProfiles();
 72 |     if (profiles.length === 0) {
 73 |       console.log(chalk.yellow('⚠ No profile files exist yet. Use "ncp config --import" to create them.'));
 74 |       return;
 75 |     }
 76 | 
 77 |     // Just show the config location and files
 78 |     console.log(chalk.green('✓ Configuration location:'));
 79 |     console.log(OutputFormatter.info(`Config directory: ${configDir}`));
 80 |     console.log(OutputFormatter.info(`Profile files:`));
 81 |     profiles.forEach(profile => {
 82 |       console.log(OutputFormatter.bullet(`${profile}.json`));
 83 |     });
 84 |     console.log('');
 85 |     console.log(chalk.dim('💡 You can edit these files directly with your preferred editor'))
 86 |   }
 87 | 
 88 |   /**
 89 |    * Import MCP configurations using interactive editor
 90 |    *
 91 |    * ⚠️ CRITICAL: Default profile MUST be 'all' - DO NOT CHANGE!
 92 |    *
 93 |    * The 'all' profile is the universal profile where MCPs are imported by default.
 94 |    * This matches the behavior of `ncp add` and auto-import functionality.
 95 |    *
 96 |    * Changing this to 'default' or any other name will break:
 97 |    * - User expectations (CLI help says "default: all")
 98 |    * - Consistency with `ncp add` command
 99 |    * - Auto-import from Claude Desktop
100 |    *
101 |    * If you change this, you WILL introduce bugs. Keep it as 'all'.
102 |    */
103 |   async importConfig(filePath?: string, profileName: string = 'all', dryRun: boolean = false): Promise<void> {
104 |     if (filePath) {
105 |       // Import from file
106 |       await this.importFromFile(filePath, profileName, dryRun);
107 |     } else {
108 |       // Interactive import with editor
109 |       await this.importInteractive(profileName, dryRun);
110 |     }
111 |   }
112 | 
113 |   /**
114 |    * Validate current configuration
115 |    */
116 |   async validateConfig(): Promise<void> {
117 |     await this.profileManager.initialize();
118 |     const configDir = this.profileManager.getConfigPath();
119 | 
120 |     if (!existsSync(configDir)) {
121 |       console.log(chalk.yellow('⚠ No config directory found. Nothing to validate.'));
122 |       return;
123 |     }
124 | 
125 |     const profiles = this.profileManager.listProfiles();
126 |     if (profiles.length === 0) {
127 |       console.log(chalk.yellow('⚠ No profile files found. Nothing to validate.'));
128 |       return;
129 |     }
130 | 
131 |     let totalMCPs = 0;
132 |     let issues: string[] = [];
133 |     let validProfiles = 0;
134 | 
135 |     for (const profileName of profiles) {
136 |       try {
137 |         const profilePath = this.profileManager.getProfilePath(profileName);
138 |         const profileContent = readFileSync(profilePath, 'utf-8');
139 |         const profile = JSON.parse(profileContent);
140 | 
141 |         // Validate profile structure
142 |         if (!profile.name) {
143 |           issues.push(`Profile "${profileName}" missing name field`);
144 |         }
145 | 
146 |         if (!profile.mcpServers || typeof profile.mcpServers !== 'object') {
147 |           issues.push(`Profile "${profileName}" missing or invalid mcpServers field`);
148 |           continue;
149 |         }
150 | 
151 |         // Validate each MCP in this profile
152 |         for (const [mcpName, mcpConfig] of Object.entries(profile.mcpServers)) {
153 |           totalMCPs++;
154 |           const config = mcpConfig as MCPConfig;
155 | 
156 |           if (!config.command) {
157 |             issues.push(`MCP "${mcpName}" in profile "${profileName}" missing command`);
158 |           }
159 | 
160 |           if (config.args && !Array.isArray(config.args)) {
161 |             issues.push(`MCP "${mcpName}" in profile "${profileName}" has invalid args (must be array)`);
162 |           }
163 | 
164 |           if (config.env && typeof config.env !== 'object') {
165 |             issues.push(`MCP "${mcpName}" in profile "${profileName}" has invalid env (must be object)`);
166 |           }
167 |         }
168 | 
169 |         validProfiles++;
170 |       } catch (error: any) {
171 |         issues.push(`Profile "${profileName}" has invalid JSON: ${error.message}`);
172 |       }
173 |     }
174 | 
175 |     if (issues.length === 0) {
176 |       console.log(chalk.green(`✓ Configuration is valid`));
177 |       console.log(chalk.blue(`  Found ${totalMCPs} MCP servers across ${validProfiles} profiles`));
178 |     } else {
179 |       console.log(chalk.red(`✗ Configuration has ${issues.length} issues:`));
180 |       issues.forEach(issue => {
181 |         console.log(chalk.red(`  • ${issue}`));
182 |       });
183 |     }
184 |   }
185 | 
186 |   /**
187 |    * Import from a JSON file
188 |    */
189 |   private async importFromFile(filePath: string, profileName: string, dryRun: boolean): Promise<void> {
190 |     // Expand tilde to home directory
191 |     const { homedir } = await import('os');
192 |     const expandedPath = filePath.startsWith('~') ?
193 |       filePath.replace('~', homedir()) :
194 |       filePath;
195 | 
196 |     if (!existsSync(expandedPath)) {
197 |       throw new Error(`Configuration file not found at: ${filePath}\n\nPlease check that the file exists and the path is correct.`);
198 |     }
199 | 
200 |     try {
201 |       const content = readFileSync(expandedPath, 'utf-8');
202 |       const parsedData = JSON.parse(content);
203 | 
204 |       // Clean the data to handle Claude Desktop format and remove unwanted entries
205 |       const mcpData = this.cleanImportData(parsedData);
206 | 
207 |       await this.processImportData(mcpData, profileName, dryRun);
208 |     } catch (error: any) {
209 |       const errorResult = ErrorHandler.handle(error, ErrorHandler.fileOperation('import', filePath));
210 |       console.log(ErrorHandler.formatForConsole(errorResult));
211 |     }
212 |   }
213 | 
214 |   /**
215 |    * Interactive import - clipboard-first approach
216 |    */
217 |   private async importInteractive(profileName: string, dryRun: boolean): Promise<void> {
218 |     console.log(chalk.blue('📋 NCP Config Import'));
219 |     console.log('');
220 | 
221 |     try {
222 |       // Try to read from clipboard
223 |       let clipboardContent = '';
224 |       try {
225 |         clipboardContent = await clipboardy.read();
226 |       } catch (clipboardError) {
227 |         console.log(chalk.red('❌ Could not access system clipboard'));
228 |         console.log(chalk.yellow('💡 Copy your MCP configuration JSON first, then run this command again'));
229 |         console.log(chalk.yellow('💡 Or use: ncp config import <file> to import from a file'));
230 |         return;
231 |       }
232 | 
233 |       // Check if clipboard has content
234 |       if (!clipboardContent.trim()) {
235 |         console.log(chalk.red('❌ Clipboard is empty'));
236 |         console.log(chalk.yellow('💡 Copy your MCP configuration JSON first, then run this command again'));
237 |         console.log(chalk.yellow('💡 Or use: ncp config import <file> to import from a file'));
238 |         console.log('');
239 |         console.log(chalk.dim('Common config file locations:'));
240 |         console.log(chalk.dim('  Claude Desktop (macOS): ~/Library/Application Support/Claude/claude_desktop_config.json'));
241 |         console.log(chalk.dim('  Claude Desktop (Windows): %APPDATA%\\Claude\\claude_desktop_config.json'));
242 |         return;
243 |       }
244 | 
245 |       // Display clipboard content in a highlighted box
246 |       console.log(chalk.blue('📋 Clipboard content detected:'));
247 |       this.displayJsonInBox(clipboardContent);
248 |       console.log('');
249 | 
250 |       // Try to parse clipboard content as JSON
251 |       let parsedData: any;
252 |       try {
253 |         parsedData = JSON.parse(clipboardContent);
254 |       } catch (jsonError) {
255 |         console.log(chalk.red('❌ Invalid JSON format in clipboard'));
256 |         console.log(chalk.yellow('💡 Please ensure your clipboard contains valid JSON'));
257 |         return;
258 |       }
259 | 
260 |       // Check if it's a direct MCP config (has "command" property at root level)
261 |       const isDirectConfig = parsedData.command && typeof parsedData === 'object' && !Array.isArray(parsedData);
262 | 
263 |       let mcpData: any;
264 |       let mcpNames: string[];
265 | 
266 |       if (isDirectConfig) {
267 |         // Handle direct MCP configuration
268 |         console.log(chalk.green('✅ Single MCP configuration detected'));
269 | 
270 |         // Prompt for name
271 |         console.log('');
272 |         console.log(chalk.blue('❓ What should we name this MCP server?'));
273 |         console.log(chalk.gray('   (e.g., \'filesystem\', \'web-search\', \'github\')'));
274 | 
275 |         const mcpName = await this.promptForMCPName(parsedData.command);
276 | 
277 |         mcpData = { [mcpName]: parsedData };
278 |         mcpNames = [mcpName];
279 |       } else {
280 |         // Handle key-value format (multiple MCPs or client config)
281 |         mcpData = this.cleanImportData(parsedData);
282 |         mcpNames = Object.keys(mcpData).filter(key => {
283 |           if (key.startsWith('//')) return false;
284 |           const config = mcpData[key];
285 |           return config && typeof config === 'object' && config.command;
286 |         });
287 | 
288 |         if (mcpNames.length > 0) {
289 |           console.log(chalk.green(`✅ ${mcpNames.length} MCP configuration(s) detected`));
290 |         } else {
291 |           console.log(chalk.red('❌ No valid MCP configurations found'));
292 |           console.log(chalk.yellow('💡 Expected JSON with MCP server configurations'));
293 |           console.log(chalk.dim('   Example: {"server": {"command": "npx", "args": ["..."]}}'));
294 |           return;
295 |         }
296 |       }
297 | 
298 |       console.log('');
299 |       await this.processImportData(mcpData, profileName, dryRun);
300 | 
301 |     } catch (error: any) {
302 |       console.log('');
303 |       const errorResult = ErrorHandler.handle(error, ErrorHandler.createContext('config', 'import', undefined, ['Check the JSON format', 'Ensure the clipboard contains valid MCP configuration']));
304 |       console.log(ErrorHandler.formatForConsole(errorResult));
305 |     }
306 |   }
307 | 
308 |   /**
309 |    * Display JSON content in a highlighted box
310 |    */
311 |   private displayJsonInBox(jsonContent: string): void {
312 |     // Pretty format the JSON for display
313 |     let formattedJson: string;
314 |     try {
315 |       const parsed = JSON.parse(jsonContent);
316 |       formattedJson = JSON.stringify(parsed, null, 2);
317 |     } catch {
318 |       // If parsing fails, use original content
319 |       formattedJson = jsonContent;
320 |     }
321 | 
322 |     // Split into lines and add box borders
323 |     const lines = formattedJson.split('\n');
324 |     const maxLength = Math.max(...lines.map(line => line.length), 20);
325 |     const boxWidth = Math.min(maxLength + 4, 80); // Limit box width to 80 chars
326 | 
327 |     // Top border
328 |     console.log(chalk.gray('┌' + '─'.repeat(boxWidth - 2) + '┐'));
329 | 
330 |     // Content lines (truncate if too long)
331 |     lines.slice(0, 20).forEach(line => { // Limit to 20 lines
332 |       let displayLine = line;
333 |       if (line.length > boxWidth - 4) {
334 |         displayLine = line.substring(0, boxWidth - 7) + '...';
335 |       }
336 |       const padding = ' '.repeat(Math.max(0, boxWidth - displayLine.length - 4));
337 |       console.log(chalk.gray('│ ') + chalk.cyan(displayLine) + padding + chalk.gray(' │'));
338 |     });
339 | 
340 |     // Show truncation indicator if there are more lines
341 |     if (lines.length > 20) {
342 |       const truncatedMsg = `... (${lines.length - 20} more lines)`;
343 |       const padding = ' '.repeat(Math.max(0, boxWidth - truncatedMsg.length - 4));
344 |       console.log(chalk.gray('│ ') + chalk.dim(truncatedMsg) + padding + chalk.gray(' │'));
345 |     }
346 | 
347 |     // Bottom border
348 |     console.log(chalk.gray('└' + '─'.repeat(boxWidth - 2) + '┘'));
349 |   }
350 | 
351 |   /**
352 |    * Process and import MCP data
353 |    */
354 |   private async processImportData(mcpData: MCPImportData, profileName: string, dryRun: boolean): Promise<void> {
355 |     await this.profileManager.initialize();
356 | 
357 |     const mcpNames = Object.keys(mcpData).filter(key => !key.startsWith('//'));
358 | 
359 |     if (mcpNames.length === 0) {
360 |       console.log(chalk.yellow('⚠ No MCP configurations found to import'));
361 |       return;
362 |     }
363 | 
364 |     if (dryRun) {
365 |       console.log('\n' + chalk.blue(`📥 Would import ${mcpNames.length} MCP server(s):`));
366 |       console.log('');
367 | 
368 |       mcpNames.forEach((name, index) => {
369 |         const config = mcpData[name];
370 |         const isLast = index === mcpNames.length - 1;
371 |         const connector = isLast ? '└──' : '├──';
372 |         const indent = isLast ? '   ' : '│  ';
373 | 
374 |         // MCP name (no indent - root level)
375 |         console.log(chalk.gray(`${connector} `) + chalk.cyan(name));
376 | 
377 |         // Command line or URL with reverse colors (like ncp list)
378 |         const fullCommand = config.url
379 |           ? `HTTP/SSE: ${config.url}`
380 |           : formatCommandDisplay(config.command || '', config.args);
381 |         const maxWidth = process.stdout.columns ? process.stdout.columns - 4 : 80;
382 |         const wrappedLines = TextUtils.wrapTextWithBackground(fullCommand, maxWidth, chalk.gray(`${indent} `), (text: string) => chalk.bgGray.black(text));
383 |         console.log(wrappedLines);
384 | 
385 |         // Environment variables if present
386 |         if (config.env && Object.keys(config.env).length > 0) {
387 |           const envCount = Object.keys(config.env).length;
388 |           console.log(chalk.gray(`${indent} `) + chalk.yellow(`${envCount} environment variable${envCount > 1 ? 's' : ''}`));
389 |         }
390 | 
391 |         if (!isLast) console.log(chalk.gray('│'));
392 |       });
393 | 
394 |       console.log('');
395 |       console.log(chalk.dim('💡 Run without --dry-run to perform the import'));
396 |       return;
397 |     }
398 | 
399 |     // Actually import the MCPs
400 |     const successful: Array<{name: string, config: MCPConfig}> = [];
401 |     const failed: Array<{name: string, error: string}> = [];
402 | 
403 |     for (const mcpName of mcpNames) {
404 |       try {
405 |         const config = mcpData[mcpName];
406 |         await this.profileManager.addMCPToProfile(profileName, mcpName, config);
407 |         successful.push({ name: mcpName, config });
408 |       } catch (error: any) {
409 |         failed.push({ name: mcpName, error: error.message });
410 |       }
411 |     }
412 | 
413 |     // Import phase completed, now validate what actually works
414 |     if (successful.length > 0) {
415 |       console.log(''); // Add newline before spinner starts
416 | 
417 |       // Show loading animation during validation
418 |       const spinner = this.createSpinner(`✅ Validating ${successful.length} imported MCP server(s)...`);
419 |       spinner.start();
420 | 
421 |       const discoveryResult = await this.discoverImportedMCPs(successful.map(s => s.name));
422 | 
423 |       // Clear spinner and show final result
424 |       spinner.stop();
425 |       process.stdout.write('\r\x1b[K'); // Clear the line
426 | 
427 |       // Show successfully working MCPs
428 |       if (discoveryResult.successful.length > 0) {
429 |         console.log(chalk.green(`✅ Successfully imported ${discoveryResult.successful.length} MCP server(s):`));
430 |         console.log('');
431 | 
432 |         // Show profile header like ncp list
433 |         console.log(`📦 ${chalk.bold.white('all')} ${chalk.dim(`(${discoveryResult.successful.length} MCPs)`)}`);
434 | 
435 |         // Show in ncp list format with rich data from fresh cache
436 |         await this.displayImportedMCPs(discoveryResult.successful);
437 |       }
438 | 
439 |       // Show MCPs that failed with actual error messages
440 |       if (discoveryResult.failed.length > 0) {
441 |         console.log(chalk.red(`❌ ${discoveryResult.failed.length} MCP(s) failed to connect:`));
442 |         discoveryResult.failed.forEach(({ name, error }) => {
443 |           console.log(chalk.red(`   • ${name}: `) + chalk.dim(error));
444 |         });
445 |         console.log('');
446 |       }
447 |     }
448 | 
449 |     if (failed.length > 0) {
450 |       console.log(chalk.red(`❌ Failed to import ${failed.length} server(s):`));
451 |       failed.forEach(({ name, error }) => {
452 |         console.log(`  ${chalk.red('•')} ${chalk.bold(name)} → ${chalk.dim(error)}`);
453 |       });
454 |       console.log('');
455 |     }
456 | 
457 |     if (successful.length > 0) {
458 |       console.log(chalk.dim('💡 Next steps:'));
459 |       console.log(chalk.dim('  •') + ' Test discovery: ' + chalk.cyan('ncp find "file tools"'));
460 |       console.log(chalk.dim('  •') + ' List all MCPs: ' + chalk.cyan('ncp list'));
461 |       console.log(chalk.dim('  •') + ' Update your AI client config to use NCP');
462 |     }
463 |   }
464 | 
465 |   /**
466 |    * Run discovery for imported MCPs to populate cache and check which ones work
467 |    * @returns Object with successful and failed MCPs with error details
468 |    */
469 |   private async discoverImportedMCPs(importedMcpNames: string[]): Promise<{successful: string[], failed: Array<{name: string, error: string}>}> {
470 |     const successful: string[] = [];
471 |     const failed: Array<{name: string, error: string}> = [];
472 | 
473 |     try {
474 |       // Import health monitor to get real error messages
475 |       const { healthMonitor } = await import('./health-monitor.js');
476 | 
477 |       // Get the imported MCP configurations for direct health checks
478 |       const profileManager = new ProfileManager();
479 |       await profileManager.initialize();
480 |       const profile = await profileManager.getProfile('all');
481 | 
482 |       if (!profile) {
483 |         throw new Error('Profile not found');
484 |       }
485 | 
486 |       // Perform direct health checks on imported MCPs
487 |       for (const mcpName of importedMcpNames) {
488 |         const mcpConfig = profile.mcpServers[mcpName];
489 |         if (!mcpConfig) {
490 |           failed.push({
491 |             name: mcpName,
492 |             error: 'MCP configuration not found in profile'
493 |           });
494 |           continue;
495 |         }
496 | 
497 |         try {
498 |           // Skip health check for HTTP/SSE MCPs (they use different connection method)
499 |           if (!mcpConfig.command && mcpConfig.url) {
500 |             logger.debug(`Skipping health check for HTTP/SSE MCP: ${mcpName}`);
501 |             continue;
502 |           }
503 | 
504 |           // Direct health check using the health monitor
505 |           const health = await healthMonitor.checkMCPHealth(
506 |             mcpName,
507 |             mcpConfig.command || '',
508 |             mcpConfig.args || [],
509 |             mcpConfig.env
510 |           );
511 | 
512 |           if (health.status === 'healthy') {
513 |             successful.push(mcpName);
514 |           } else {
515 |             failed.push({
516 |               name: mcpName,
517 |               error: health.lastError || health.disabledReason || 'Health check failed'
518 |             });
519 |           }
520 |         } catch (error) {
521 |           failed.push({
522 |             name: mcpName,
523 |             error: `Health check error: ${error instanceof Error ? error.message : 'Unknown error'}`
524 |           });
525 |         }
526 |       }
527 | 
528 |       // If we have successful MCPs, run discovery to populate cache for display
529 |       if (successful.length > 0) {
530 |         try {
531 |           const { NCPOrchestrator } = await import('../orchestrator/ncp-orchestrator.js');
532 |           const orchestrator = new NCPOrchestrator();
533 |           await orchestrator.initialize();
534 |           await orchestrator.find('', 1000, false);
535 |           await orchestrator.cleanup();
536 |         } catch (error) {
537 |           // Discovery failure doesn't affect health check results, just cache population
538 |           console.log('Cache population failed, but health checks completed');
539 |         }
540 |       }
541 | 
542 |     } catch (error) {
543 |       // If the entire process fails, all are considered failed
544 |       for (const mcpName of importedMcpNames) {
545 |         failed.push({
546 |           name: mcpName,
547 |           error: `Discovery failed: ${error instanceof Error ? error.message : 'Unknown error'}`
548 |         });
549 |       }
550 |     }
551 | 
552 |     return { successful, failed };
553 |   }
554 | 
555 |   /**
556 |    * Display imported MCPs in ncp list style with rich data (descriptions, versions, tool counts)
557 |    */
558 |   private async displayImportedMCPs(importedMcpNames: string[]): Promise<void> {
559 |     // Load cache data for rich display
560 |     const mcpDescriptions: Record<string, string> = {};
561 |     const mcpToolCounts: Record<string, number> = {};
562 |     const mcpVersions: Record<string, string> = {};
563 | 
564 |     await this.loadMCPInfoFromCache(mcpDescriptions, mcpToolCounts, mcpVersions);
565 | 
566 |     // Get the imported MCPs' configurations
567 |     const profiles = this.profileManager.listProfiles();
568 |     const allMcps: Record<string, MCPConfig> = {};
569 | 
570 |     // Collect all MCPs from all profiles to get the config
571 |     for (const profileName of profiles) {
572 |       try {
573 |         const profileConfig = await this.profileManager.getProfile(profileName);
574 |         if (profileConfig?.mcpServers) {
575 |           Object.assign(allMcps, profileConfig.mcpServers);
576 |         }
577 |       } catch (error) {
578 |         // Skip invalid profiles
579 |       }
580 |     }
581 | 
582 |     // Filter to only show imported MCPs
583 |     const filteredMcps: Record<string, MCPConfig> = {};
584 |     for (const mcpName of importedMcpNames) {
585 |       if (allMcps[mcpName]) {
586 |         filteredMcps[mcpName] = allMcps[mcpName];
587 |       }
588 |     }
589 | 
590 |     if (Object.keys(filteredMcps).length === 0) {
591 |       console.log(chalk.yellow('⚠ No imported MCPs found to display'));
592 |       return;
593 |     }
594 | 
595 |     // Display without the "all" header - just show imported MCPs directly
596 | 
597 |     const mcpEntries = Object.entries(filteredMcps);
598 |     mcpEntries.forEach(([mcpName, config], index) => {
599 |       const isLast = index === mcpEntries.length - 1;
600 |       const connector = isLast ? '└──' : '├──';
601 |       const indent = isLast ? '   ' : '│  ';
602 | 
603 |       // MCP name with tool count and version (like ncp list) - handle case variations
604 |       const capitalizedName = mcpName.charAt(0).toUpperCase() + mcpName.slice(1);
605 |       const toolCount = mcpToolCounts[mcpName] ?? mcpToolCounts[capitalizedName];
606 |       const versionPart = (mcpVersions[mcpName] ?? mcpVersions[capitalizedName]) ?
607 |                          chalk.magenta(`v${mcpVersions[mcpName] ?? mcpVersions[capitalizedName]}`) : '';
608 |       const toolPart = toolCount !== undefined ? chalk.green(`${toolCount} ${toolCount === 1 ? 'tool' : 'tools'}`) : '';
609 | 
610 |       let nameDisplay = chalk.bold.cyanBright(mcpName);
611 | 
612 |       // Format: (v1.0.0 | 4 tools) with version first, all inside parentheses - like ncp list
613 |       const badge = versionPart && toolPart ? chalk.dim(` (${versionPart} | ${toolPart})`) :
614 |                    versionPart ? chalk.dim(` (${versionPart})`) :
615 |                    toolPart ? chalk.dim(` (${toolPart})`) : '';
616 | 
617 |       nameDisplay += badge;
618 | 
619 |       // Indent properly under the profile (like ncp list)
620 |       console.log(`  ${connector} ${nameDisplay}`);
621 | 
622 |       // Description if available (depth >= 1)
623 |       const description = mcpDescriptions[mcpName];
624 |       if (description && description.toLowerCase() !== mcpName.toLowerCase()) {
625 |         console.log(`  ${indent} ${chalk.white(description)}`);
626 |       }
627 | 
628 |       // Command or URL with reverse colors (depth >= 2)
629 |       const commandText = config.url
630 |         ? `HTTP/SSE: ${config.url}`
631 |         : formatCommandDisplay(config.command || '', config.args);
632 |       const maxWidth = process.stdout.columns ? process.stdout.columns - 6 : 80;
633 |       const wrappedLines = TextUtils.wrapTextWithBackground(commandText, maxWidth, `  ${indent} `, (text: string) => chalk.bgGray.black(text));
634 |       console.log(wrappedLines);
635 | 
636 |       if (!isLast) console.log(`  │`);
637 |     });
638 | 
639 |     console.log('');
640 |   }
641 | 
642 |   /**
643 |    * Load MCP info from cache (copied from CLI list command)
644 |    */
645 |   private async loadMCPInfoFromCache(
646 |     mcpDescriptions: Record<string, string>,
647 |     mcpToolCounts: Record<string, number>,
648 |     mcpVersions: Record<string, string>
649 |   ): Promise<boolean> {
650 |     try {
651 |       const { readFileSync, existsSync } = await import('fs');
652 |       const { join } = await import('path');
653 |       const { homedir } = await import('os');
654 | 
655 |       const cacheDir = join(homedir(), '.ncp', 'cache');
656 |       const cachePath = join(cacheDir, 'all-tools.json');
657 | 
658 |       if (!existsSync(cachePath)) {
659 |         return false; // No cache available
660 |       }
661 | 
662 |       const cacheContent = readFileSync(cachePath, 'utf-8');
663 |       const cache = JSON.parse(cacheContent);
664 | 
665 |       // Extract server info and tool counts from cache
666 |       for (const [mcpName, mcpData] of Object.entries(cache.mcps || {})) {
667 |         const data = mcpData as any;
668 | 
669 |         // Extract server description (without version)
670 |         if (data.serverInfo?.description && data.serverInfo.description !== mcpName) {
671 |           mcpDescriptions[mcpName] = data.serverInfo.description;
672 |         } else if (data.serverInfo?.title) {
673 |           mcpDescriptions[mcpName] = data.serverInfo.title;
674 |         }
675 | 
676 |         // Extract version separately
677 |         if (data.serverInfo?.version && data.serverInfo.version !== 'unknown') {
678 |           mcpVersions[mcpName] = data.serverInfo.version;
679 |         }
680 | 
681 |         // Count tools
682 |         if (data.tools && Array.isArray(data.tools)) {
683 |           mcpToolCounts[mcpName] = data.tools.length;
684 |         }
685 |       }
686 |       return true;
687 |     } catch (error) {
688 |       // No cache available - just show basic info
689 |       return false;
690 |     }
691 |   }
692 | 
693 |   /**
694 |    * Create a simple spinner for loading animation
695 |    */
696 |   private createSpinner(message: string) {
697 |     const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
698 |     let i = 0;
699 |     let intervalId: NodeJS.Timeout;
700 | 
701 |     return {
702 |       start: () => {
703 |         intervalId = setInterval(() => {
704 |           process.stdout.write(`\r${chalk.dim(frames[i % frames.length])} ${message}`);
705 |           i++;
706 |         }, 100);
707 |       },
708 |       stop: () => {
709 |         if (intervalId) {
710 |           clearInterval(intervalId);
711 |         }
712 |       }
713 |     };
714 |   }
715 | 
716 |   /**
717 |    * Clean template comments and example data from import
718 |    */
719 |   private cleanImportData(data: any): MCPImportData {
720 |     const cleaned: MCPImportData = {};
721 | 
722 |     // Check if this is a Claude Desktop config format with mcpServers wrapper
723 |     if (data.mcpServers && typeof data.mcpServers === 'object') {
724 |       data = data.mcpServers;
725 |     }
726 | 
727 |     for (const [key, value] of Object.entries(data)) {
728 |       // Skip template comments and example sections
729 |       if (key.startsWith('//') || key.includes('Example') || key.includes('Your MCPs')) {
730 |         continue;
731 |       }
732 | 
733 |       // Skip NCP entries themselves to avoid circular references
734 |       if (key.toLowerCase().startsWith('ncp')) {
735 |         continue;
736 |       }
737 | 
738 |       // Validate that value is a valid MCP config object
739 |       if (value && typeof value === 'object' && !Array.isArray(value)) {
740 |         const mcpConfig = value as any;
741 |         // Must have a command property to be valid
742 |         if (mcpConfig.command && typeof mcpConfig.command === 'string') {
743 |           cleaned[key] = mcpConfig as MCPConfig;
744 |         }
745 |       }
746 |     }
747 | 
748 |     return cleaned;
749 |   }
750 | 
751 | 
752 |   /**
753 |    * Prompt user for MCP name with smart suggestions
754 |    */
755 |   private async promptForMCPName(command: string): Promise<string> {
756 |     const rl = createInterface({
757 |       input: process.stdin,
758 |       output: process.stdout
759 |     });
760 | 
761 |     // Generate smart suggestion based on command
762 |     const suggestion = this.generateMCPNameSuggestion(command);
763 | 
764 |     return new Promise((resolve) => {
765 |       const prompt = suggestion
766 |         ? `➤ MCP name [${chalk.cyan(suggestion)}]: `
767 |         : `➤ MCP name: `;
768 | 
769 |       rl.question(prompt, (answer) => {
770 |         rl.close();
771 |         const finalName = answer.trim() || suggestion || 'unnamed-mcp';
772 |         console.log(chalk.green(`  ✅ Using name: '${finalName}'`));
773 |         resolve(finalName);
774 |       });
775 |     });
776 |   }
777 | 
778 |   /**
779 |    * Generate smart MCP name suggestions based on command
780 |    */
781 |   private generateMCPNameSuggestion(command: string): string {
782 |     // Remove common prefixes and suffixes
783 |     let suggestion = command
784 |       .replace(/^mcp-/, '')           // Remove "mcp-" prefix
785 |       .replace(/-server$/, '')        // Remove "-server" suffix
786 |       .replace(/-mcp$/, '')           // Remove "-mcp" suffix
787 |       .replace(/^@[\w-]+\//, '')      // Remove npm scope like "@org/"
788 |       .toLowerCase();
789 | 
790 |     // Handle common patterns
791 |     const patterns: Record<string, string> = {
792 |       'filesystem': 'filesystem',
793 |       'file': 'filesystem',
794 |       'web-search': 'web',
795 |       'search': 'web-search',
796 |       'github': 'github',
797 |       'git': 'git',
798 |       'database': 'database',
799 |       'db': 'database',
800 |       'shell': 'shell',
801 |       'terminal': 'shell'
802 |     };
803 | 
804 |     return patterns[suggestion] || suggestion || 'mcp-server';
805 |   }
806 | }
```

--------------------------------------------------------------------------------
/test/mcp-ecosystem-discovery.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Comprehensive MCP Ecosystem Discovery Tests
  3 |  * Tests 1000+ battle-tested MCPs with real descriptions but fake implementations
  4 |  * Validates user story → tool discovery across the entire MCP ecosystem
  5 |  */
  6 | 
  7 | import { DiscoveryEngine } from '../src/discovery/engine';
  8 | import { MCPDomainAnalyzer } from '../src/discovery/mcp-domain-analyzer';
  9 | 
 10 | // Test MCP with real descriptions but fake implementation
 11 | interface TestMCP {
 12 |   name: string;
 13 |   description: string;
 14 |   category: string;
 15 |   tools: Array<{
 16 |     name: string;
 17 |     description: string;
 18 |     parameters?: Record<string, string>;
 19 |   }>;
 20 | }
 21 | 
 22 | // Battle-tested MCPs from real ecosystem
 23 | const ECOSYSTEM_TEST_MCPS: TestMCP[] = [
 24 |   // Database MCPs
 25 |   {
 26 |     name: 'postgres',
 27 |     description: 'PostgreSQL database operations including queries, schema management, and data manipulation',
 28 |     category: 'database',
 29 |     tools: [
 30 |       {
 31 |         name: 'query',
 32 |         description: 'Execute SQL queries to retrieve data from PostgreSQL database tables',
 33 |         parameters: {
 34 |           query: 'SQL query string to execute',
 35 |           params: 'Optional parameters for parameterized queries'
 36 |         }
 37 |       },
 38 |       {
 39 |         name: 'insert',
 40 |         description: 'Insert new records into PostgreSQL database tables',
 41 |         parameters: {
 42 |           table: 'Target table name',
 43 |           data: 'Record data to insert'
 44 |         }
 45 |       },
 46 |       {
 47 |         name: 'update',
 48 |         description: 'Update existing records in PostgreSQL database tables',
 49 |         parameters: {
 50 |           table: 'Target table name',
 51 |           data: 'Updated record data',
 52 |           where: 'WHERE clause conditions'
 53 |         }
 54 |       },
 55 |       {
 56 |         name: 'delete',
 57 |         description: 'Delete records from PostgreSQL database tables',
 58 |         parameters: {
 59 |           table: 'Target table name',
 60 |           where: 'WHERE clause conditions'
 61 |         }
 62 |       },
 63 |       {
 64 |         name: 'create_table',
 65 |         description: 'Create new tables in PostgreSQL database with schema definition',
 66 |         parameters: {
 67 |           name: 'Table name',
 68 |           schema: 'Table schema definition'
 69 |         }
 70 |       }
 71 |     ]
 72 |   },
 73 | 
 74 |   {
 75 |     name: 'stripe',
 76 |     description: 'Complete payment processing for online businesses including charges, subscriptions, and refunds',
 77 |     category: 'financial',
 78 |     tools: [
 79 |       {
 80 |         name: 'create_payment',
 81 |         description: 'Process credit card payments and charges from customers',
 82 |         parameters: {
 83 |           amount: 'Payment amount in cents',
 84 |           currency: 'Three-letter currency code',
 85 |           customer: 'Customer identifier'
 86 |         }
 87 |       },
 88 |       {
 89 |         name: 'refund_payment',
 90 |         description: 'Process refunds for previously charged payments',
 91 |         parameters: {
 92 |           payment_id: 'Original payment identifier',
 93 |           amount: 'Refund amount in cents',
 94 |           reason: 'Reason for refund'
 95 |         }
 96 |       },
 97 |       {
 98 |         name: 'create_subscription',
 99 |         description: 'Create recurring subscription billing for customers',
100 |         parameters: {
101 |           customer: 'Customer identifier',
102 |           price: 'Subscription price identifier',
103 |           trial_days: 'Optional trial period in days'
104 |         }
105 |       },
106 |       {
107 |         name: 'list_payments',
108 |         description: 'List payment transactions with filtering and pagination',
109 |         parameters: {
110 |           customer: 'Optional customer filter',
111 |           date_range: 'Optional date range filter',
112 |           status: 'Optional payment status filter'
113 |         }
114 |       }
115 |     ]
116 |   },
117 | 
118 |   {
119 |     name: 'github',
120 |     description: 'GitHub API integration for repository management, file operations, issues, and pull requests',
121 |     category: 'developer-tools',
122 |     tools: [
123 |       {
124 |         name: 'create_repository',
125 |         description: 'Create new GitHub repositories with initial setup',
126 |         parameters: {
127 |           name: 'Repository name',
128 |           description: 'Repository description',
129 |           private: 'Whether repository should be private'
130 |         }
131 |       },
132 |       {
133 |         name: 'create_issue',
134 |         description: 'Create new issues in GitHub repositories for bug tracking',
135 |         parameters: {
136 |           repo: 'Repository name',
137 |           title: 'Issue title',
138 |           body: 'Issue description',
139 |           labels: 'Issue labels'
140 |         }
141 |       },
142 |       {
143 |         name: 'create_pull_request',
144 |         description: 'Create pull requests for code review and collaboration',
145 |         parameters: {
146 |           repo: 'Repository name',
147 |           title: 'Pull request title',
148 |           body: 'Pull request description',
149 |           head: 'Source branch',
150 |           base: 'Target branch'
151 |         }
152 |       },
153 |       {
154 |         name: 'get_file',
155 |         description: 'Retrieve file contents from GitHub repositories',
156 |         parameters: {
157 |           repo: 'Repository name',
158 |           path: 'File path',
159 |           branch: 'Optional branch name'
160 |         }
161 |       },
162 |       {
163 |         name: 'search_repositories',
164 |         description: 'Search for repositories across GitHub with various filters',
165 |         parameters: {
166 |           query: 'Search query',
167 |           language: 'Programming language filter',
168 |           sort: 'Sort criteria'
169 |         }
170 |       }
171 |     ]
172 |   },
173 | 
174 |   {
175 |     name: 'slack',
176 |     description: 'Slack integration for messaging, channel management, file sharing, and team communication',
177 |     category: 'communication',
178 |     tools: [
179 |       {
180 |         name: 'send_message',
181 |         description: 'Send messages to Slack channels or direct messages',
182 |         parameters: {
183 |           channel: 'Channel ID or name',
184 |           text: 'Message content',
185 |           thread_ts: 'Optional thread timestamp for replies'
186 |         }
187 |       },
188 |       {
189 |         name: 'create_channel',
190 |         description: 'Create new Slack channels for team communication',
191 |         parameters: {
192 |           name: 'Channel name',
193 |           purpose: 'Channel purpose description',
194 |           private: 'Whether channel should be private'
195 |         }
196 |       },
197 |       {
198 |         name: 'upload_file',
199 |         description: 'Upload files to Slack channels for sharing',
200 |         parameters: {
201 |           file: 'File to upload',
202 |           channel: 'Target channel',
203 |           title: 'File title',
204 |           comment: 'Optional file comment'
205 |         }
206 |       },
207 |       {
208 |         name: 'get_channel_history',
209 |         description: 'Retrieve message history from Slack channels',
210 |         parameters: {
211 |           channel: 'Channel ID',
212 |           count: 'Number of messages to retrieve',
213 |           oldest: 'Oldest timestamp for filtering'
214 |         }
215 |       }
216 |     ]
217 |   },
218 | 
219 |   {
220 |     name: 'playwright',
221 |     description: 'Browser automation and web scraping with cross-browser support',
222 |     category: 'web-automation',
223 |     tools: [
224 |       {
225 |         name: 'navigate',
226 |         description: 'Navigate browser to specified URL for web automation',
227 |         parameters: {
228 |           url: 'Target URL to navigate to',
229 |           wait_until: 'Wait condition (load, networkidle, etc.)'
230 |         }
231 |       },
232 |       {
233 |         name: 'click_element',
234 |         description: 'Click on web page elements using various selectors',
235 |         parameters: {
236 |           selector: 'CSS selector or XPath',
237 |           timeout: 'Maximum wait time in milliseconds'
238 |         }
239 |       },
240 |       {
241 |         name: 'extract_text',
242 |         description: 'Extract text content from web page elements',
243 |         parameters: {
244 |           selector: 'CSS selector for target elements',
245 |           attribute: 'Optional attribute to extract instead of text'
246 |         }
247 |       },
248 |       {
249 |         name: 'fill_form',
250 |         description: 'Fill out web forms with specified data',
251 |         parameters: {
252 |           form_data: 'Key-value pairs of form field data',
253 |           submit: 'Whether to submit form after filling'
254 |         }
255 |       },
256 |       {
257 |         name: 'take_screenshot',
258 |         description: 'Capture screenshots of web pages for documentation',
259 |         parameters: {
260 |           path: 'Output file path',
261 |           full_page: 'Whether to capture full page or viewport only'
262 |         }
263 |       }
264 |     ]
265 |   },
266 | 
267 |   {
268 |     name: 'aws',
269 |     description: 'Amazon Web Services integration for EC2, S3, Lambda, and cloud resource management',
270 |     category: 'cloud-infrastructure',
271 |     tools: [
272 |       {
273 |         name: 'create_ec2_instance',
274 |         description: 'Launch new EC2 instances for compute workloads',
275 |         parameters: {
276 |           instance_type: 'EC2 instance type (t2.micro, etc.)',
277 |           ami_id: 'Amazon Machine Image identifier',
278 |           key_pair: 'SSH key pair name',
279 |           security_group: 'Security group identifier'
280 |         }
281 |       },
282 |       {
283 |         name: 'upload_to_s3',
284 |         description: 'Upload files to S3 buckets for cloud storage',
285 |         parameters: {
286 |           bucket: 'S3 bucket name',
287 |           key: 'Object key/path',
288 |           file: 'File to upload',
289 |           acl: 'Access control permissions'
290 |         }
291 |       },
292 |       {
293 |         name: 'create_lambda',
294 |         description: 'Create AWS Lambda functions for serverless computing',
295 |         parameters: {
296 |           function_name: 'Lambda function name',
297 |           runtime: 'Runtime environment (python3.9, nodejs18.x, etc.)',
298 |           code: 'Function code or ZIP file',
299 |           handler: 'Function handler specification'
300 |         }
301 |       },
302 |       {
303 |         name: 'list_resources',
304 |         description: 'List AWS resources across different services',
305 |         parameters: {
306 |           service: 'AWS service name (ec2, s3, lambda, etc.)',
307 |           region: 'AWS region',
308 |           filters: 'Optional resource filters'
309 |         }
310 |       }
311 |     ]
312 |   },
313 | 
314 |   {
315 |     name: 'filesystem',
316 |     description: 'Local file system operations including reading, writing, directory management, and permissions',
317 |     category: 'file-operations',
318 |     tools: [
319 |       {
320 |         name: 'read_file',
321 |         description: 'Read the contents of files from the local file system',
322 |         parameters: {
323 |           path: 'File path to read',
324 |           encoding: 'File encoding (utf-8, binary, etc.)'
325 |         }
326 |       },
327 |       {
328 |         name: 'write_file',
329 |         description: 'Write or create files in the local file system',
330 |         parameters: {
331 |           path: 'File path to write',
332 |           content: 'File content to write',
333 |           encoding: 'File encoding',
334 |           append: 'Whether to append to existing file'
335 |         }
336 |       },
337 |       {
338 |         name: 'create_directory',
339 |         description: 'Create new directories in the file system',
340 |         parameters: {
341 |           path: 'Directory path to create',
342 |           recursive: 'Whether to create parent directories'
343 |         }
344 |       },
345 |       {
346 |         name: 'list_directory',
347 |         description: 'List contents of directories with file information',
348 |         parameters: {
349 |           path: 'Directory path to list',
350 |           include_hidden: 'Whether to include hidden files',
351 |           recursive: 'Whether to list subdirectories recursively'
352 |         }
353 |       },
354 |       {
355 |         name: 'copy_file',
356 |         description: 'Copy files to different locations for backup or organization',
357 |         parameters: {
358 |           source: 'Source file path',
359 |           destination: 'Destination file path',
360 |           overwrite: 'Whether to overwrite existing files'
361 |         }
362 |       },
363 |       {
364 |         name: 'delete_file',
365 |         description: 'Delete files or directories from the file system',
366 |         parameters: {
367 |           path: 'Path to delete',
368 |           recursive: 'Whether to delete directories recursively',
369 |           force: 'Whether to force deletion'
370 |         }
371 |       }
372 |     ]
373 |   },
374 | 
375 |   {
376 |     name: 'shell',
377 |     description: 'Execute shell commands and system operations including scripts, processes, and system management',
378 |     category: 'system-operations',
379 |     tools: [
380 |       {
381 |         name: 'run_command',
382 |         description: 'Execute shell commands and system operations with output capture',
383 |         parameters: {
384 |           command: 'Shell command to execute',
385 |           args: 'Command arguments array',
386 |           cwd: 'Working directory for command execution',
387 |           env: 'Environment variables'
388 |         }
389 |       },
390 |       {
391 |         name: 'run_script',
392 |         description: 'Execute shell scripts with parameter passing',
393 |         parameters: {
394 |           script_path: 'Path to script file',
395 |           args: 'Script arguments',
396 |           interpreter: 'Script interpreter (bash, python, etc.)'
397 |         }
398 |       }
399 |     ]
400 |   },
401 | 
402 |   {
403 |     name: 'git',
404 |     description: 'Git version control operations including commits, branches, merges, and repository management',
405 |     category: 'developer-tools',
406 |     tools: [
407 |       {
408 |         name: 'commit',
409 |         description: 'Commit changes to git repository with message',
410 |         parameters: {
411 |           message: 'Commit message',
412 |           files: 'Optional specific files to commit',
413 |           all: 'Whether to commit all staged changes'
414 |         }
415 |       },
416 |       {
417 |         name: 'create_branch',
418 |         description: 'Create new git branches for feature development',
419 |         parameters: {
420 |           name: 'Branch name',
421 |           from: 'Optional source branch or commit'
422 |         }
423 |       },
424 |       {
425 |         name: 'merge',
426 |         description: 'Merge git branches with conflict resolution',
427 |         parameters: {
428 |           branch: 'Branch to merge',
429 |           strategy: 'Merge strategy',
430 |           message: 'Optional merge message'
431 |         }
432 |       },
433 |       {
434 |         name: 'push',
435 |         description: 'Push commits to remote git repositories',
436 |         parameters: {
437 |           remote: 'Remote repository name',
438 |           branch: 'Branch to push',
439 |           force: 'Whether to force push'
440 |         }
441 |       },
442 |       {
443 |         name: 'pull',
444 |         description: 'Pull latest changes from remote git repositories',
445 |         parameters: {
446 |           remote: 'Remote repository name',
447 |           branch: 'Branch to pull from',
448 |           rebase: 'Whether to rebase instead of merge'
449 |         }
450 |       }
451 |     ]
452 |   },
453 | 
454 |   {
455 |     name: 'notion',
456 |     description: 'Notion workspace management for documents, databases, and collaborative content creation',
457 |     category: 'productivity',
458 |     tools: [
459 |       {
460 |         name: 'create_page',
461 |         description: 'Create new pages in Notion workspaces',
462 |         parameters: {
463 |           title: 'Page title',
464 |           parent: 'Parent page or database ID',
465 |           content: 'Page content blocks'
466 |         }
467 |       },
468 |       {
469 |         name: 'update_database',
470 |         description: 'Update records in Notion databases',
471 |         parameters: {
472 |           database_id: 'Notion database identifier',
473 |           page_id: 'Specific page/record to update',
474 |           properties: 'Properties to update'
475 |         }
476 |       },
477 |       {
478 |         name: 'search_content',
479 |         description: 'Search across Notion workspace for content',
480 |         parameters: {
481 |           query: 'Search query string',
482 |           filter: 'Optional content type filter',
483 |           sort: 'Sort criteria for results'
484 |         }
485 |       }
486 |     ]
487 |   }
488 | ];
489 | 
490 | describe.skip('MCP Ecosystem Discovery Tests', () => {
491 |   let engine: DiscoveryEngine;
492 |   let domainAnalyzer: MCPDomainAnalyzer;
493 | 
494 |   // Track test results for overall success rate
495 |   const testResults = { passed: 0, failed: 0 };
496 | 
497 |   beforeAll(async () => {
498 |     engine = new DiscoveryEngine();
499 |     domainAnalyzer = new MCPDomainAnalyzer();
500 |     await engine.initialize();
501 | 
502 |     // Clear any existing cached tools to ensure clean test environment
503 |     await engine['ragEngine'].clearCache();
504 | 
505 |     // Index all test MCPs
506 |     for (const testMcp of ECOSYSTEM_TEST_MCPS) {
507 |       await engine.indexMCPTools(testMcp.name, testMcp.tools);
508 |     }
509 |   });
510 | 
511 |   describe('Database Operations User Stories', () => {
512 |     test('I need to find customer orders from the last month', async () => {
513 |       const results = await engine.findRelevantTools('I need to find customer orders from the last month', 5);
514 |       const topTools = results.map(r => r.name);
515 | 
516 |       expect(topTools.some(t =>
517 |         t === 'postgres:query' ||
518 |         t.includes('query') ||
519 |         t.includes('search')
520 |       )).toBeTruthy();
521 |     });
522 | 
523 |     test('I want to update customer email addresses', async () => {
524 |       const results = await engine.findRelevantTools('I want to update customer email addresses', 5);
525 |       const topTools = results.map(r => r.name);
526 | 
527 |       // Debug: Log what tools are actually returned
528 |       console.log('Update email query returned:', topTools);
529 | 
530 |       const hasUpdateTool = topTools.some(t =>
531 |         t === 'postgres:update' ||
532 |         t.includes('update')
533 |       );
534 | 
535 |       if (!hasUpdateTool) {
536 |         console.log('Expected postgres:update or update tool, but got:', topTools);
537 |       }
538 | 
539 |       expect(hasUpdateTool).toBeTruthy();
540 |     });
541 | 
542 |     test('I need to store new customer information', async () => {
543 |       const results = await engine.findRelevantTools('I need to store new customer information', 5);
544 |       const topTools = results.map(r => r.name);
545 | 
546 |       expect(topTools.some(t =>
547 |         t === 'postgres:insert' ||
548 |         t.includes('insert') ||
549 |         t.includes('create')
550 |       )).toBeTruthy();
551 |     });
552 | 
553 |     test('I want to create a new table for user sessions', async () => {
554 |       const results = await engine.findRelevantTools('I want to create a new table for user sessions', 5);
555 |       const topTools = results.map(r => r.name);
556 | 
557 |       expect(topTools.some(t =>
558 |         t === 'postgres:create_table' ||
559 |         t.includes('create_table')
560 |       )).toBeTruthy();
561 |     });
562 |   });
563 | 
564 |   describe('Payment Processing User Stories', () => {
565 |     test('I need to charge a customer for their order', async () => {
566 |       const results = await engine.findRelevantTools('I need to charge a customer for their order', 5);
567 |       const topTools = results.map(r => r.name);
568 | 
569 |       expect(topTools.some(t =>
570 |         t === 'stripe:create_payment' ||
571 |         t.includes('payment') ||
572 |         t.includes('charge')
573 |       )).toBeTruthy();
574 |     });
575 | 
576 |     test('I want to refund a cancelled subscription', async () => {
577 |       const results = await engine.findRelevantTools('I want to refund a cancelled subscription', 5);
578 |       const topTools = results.map(r => r.name);
579 | 
580 |       expect(topTools.some(t =>
581 |         t === 'stripe:refund_payment' ||
582 |         t.includes('refund')
583 |       )).toBeTruthy();
584 |     });
585 | 
586 |     test('I need to set up monthly billing for customers', async () => {
587 |       const results = await engine.findRelevantTools('I need to set up monthly billing for customers', 5);
588 |       const topTools = results.map(r => r.name);
589 | 
590 |       expect(topTools.some(t =>
591 |         t === 'stripe:create_subscription' ||
592 |         t.includes('subscription')
593 |       )).toBeTruthy();
594 |     });
595 | 
596 |     test('I want to see all payment transactions from today', async () => {
597 |       const results = await engine.findRelevantTools('I want to see all payment transactions from today', 5);
598 |       const topTools = results.map(r => r.name);
599 | 
600 |       expect(topTools.some(t =>
601 |         t === 'stripe:list_payments' ||
602 |         t.includes('list') ||
603 |         t.includes('payment')
604 |       )).toBeTruthy();
605 |     });
606 |   });
607 | 
608 |   describe('Developer Tools User Stories', () => {
609 |     test('I want to save my code changes', async () => {
610 |       const results = await engine.findRelevantTools('I want to save my code changes', 5);
611 |       const topTools = results.map(r => r.name);
612 | 
613 |       expect(topTools.some(t =>
614 |         t === 'git:commit' ||
615 |         t.includes('commit')
616 |       )).toBeTruthy();
617 |     });
618 | 
619 |     test('I need to create a new feature branch', async () => {
620 |       const results = await engine.findRelevantTools('I need to create a new feature branch', 5);
621 |       const topTools = results.map(r => r.name);
622 | 
623 |       expect(topTools.some(t =>
624 |         t === 'git:create_branch' ||
625 |         t.includes('branch')
626 |       )).toBeTruthy();
627 |     });
628 | 
629 |     test('I want to share my code with the team', async () => {
630 |       const results = await engine.findRelevantTools('I want to share my code with the team', 5);
631 |       const topTools = results.map(r => r.name);
632 | 
633 |       expect(topTools.some(t =>
634 |         t === 'git:push' ||
635 |         t === 'github:create_pull_request' ||
636 |         t.includes('push') ||
637 |         t.includes('pull_request')
638 |       )).toBeTruthy();
639 |     });
640 | 
641 |     test('I need to create a new repository for my project', async () => {
642 |       const results = await engine.findRelevantTools('I need to create a new repository for my project', 5);
643 |       const topTools = results.map(r => r.name);
644 | 
645 |       expect(topTools.some(t =>
646 |         t === 'github:create_repository' ||
647 |         t.includes('repository')
648 |       )).toBeTruthy();
649 |     });
650 | 
651 |     test('I want to report a bug in the project', async () => {
652 |       const results = await engine.findRelevantTools('I want to report a bug in the project', 5);
653 |       const topTools = results.map(r => r.name);
654 | 
655 |       expect(topTools.some(t =>
656 |         t === 'github:create_issue' ||
657 |         t.includes('issue')
658 |       )).toBeTruthy();
659 |     });
660 |   });
661 | 
662 |   describe('Communication User Stories', () => {
663 |     test('I need to notify the team about deployment', async () => {
664 |       const results = await engine.findRelevantTools('I need to notify the team about deployment', 5);
665 |       const topTools = results.map(r => r.name);
666 | 
667 |       expect(topTools.some(t =>
668 |         t === 'slack:send_message' ||
669 |         t.includes('message') ||
670 |         t.includes('send')
671 |       )).toBeTruthy();
672 |     });
673 | 
674 |     test('I want to create a channel for project discussion', async () => {
675 |       const results = await engine.findRelevantTools('I want to create a channel for project discussion', 5);
676 |       const topTools = results.map(r => r.name);
677 | 
678 |       expect(topTools.some(t =>
679 |         t === 'slack:create_channel' ||
680 |         t.includes('channel')
681 |       )).toBeTruthy();
682 |     });
683 | 
684 |     test('I need to share documents with the team', async () => {
685 |       const results = await engine.findRelevantTools('I need to share documents with the team', 5);
686 |       const topTools = results.map(r => r.name);
687 | 
688 |       expect(topTools.some(t =>
689 |         t === 'slack:upload_file' ||
690 |         t.includes('upload') ||
691 |         t.includes('file')
692 |       )).toBeTruthy();
693 |     });
694 |   });
695 | 
696 |   describe('Web Automation User Stories', () => {
697 |     test('I want to scrape product data from a website', async () => {
698 |       const results = await engine.findRelevantTools('I want to scrape product data from a website', 5);
699 |       const topTools = results.map(r => r.name);
700 | 
701 |       expect(topTools.some(t =>
702 |         t.includes('playwright') ||
703 |         t.includes('extract') ||
704 |         t.includes('scrape')
705 |       )).toBeTruthy();
706 |     });
707 | 
708 |     test('I need to fill out a registration form automatically', async () => {
709 |       const results = await engine.findRelevantTools('I need to fill out a registration form automatically', 5);
710 |       const topTools = results.map(r => r.name);
711 | 
712 |       expect(topTools.some(t =>
713 |         t === 'playwright:fill_form' ||
714 |         t.includes('form') ||
715 |         t.includes('fill')
716 |       )).toBeTruthy();
717 |     });
718 | 
719 |     test('I want to take screenshots of web pages', async () => {
720 |       const results = await engine.findRelevantTools('I want to take screenshots of web pages', 5);
721 |       const topTools = results.map(r => r.name);
722 | 
723 |       expect(topTools.some(t =>
724 |         t === 'playwright:take_screenshot' ||
725 |         t.includes('screenshot')
726 |       )).toBeTruthy();
727 |     });
728 |   });
729 | 
730 |   describe('Cloud Infrastructure User Stories', () => {
731 |     test('I need to deploy my application to the cloud', async () => {
732 |       const results = await engine.findRelevantTools('I need to deploy my application to the cloud', 5);
733 |       const topTools = results.map(r => r.name);
734 | 
735 |       expect(topTools.some(t =>
736 |         t.includes('aws') ||
737 |         t.includes('ec2') ||
738 |         t.includes('lambda') ||
739 |         t.includes('deploy')
740 |       )).toBeTruthy();
741 |     });
742 | 
743 |     test('I want to upload files to cloud storage', async () => {
744 |       const results = await engine.findRelevantTools('I want to upload files to cloud storage', 5);
745 |       const topTools = results.map(r => r.name);
746 | 
747 |       expect(topTools.some(t =>
748 |         t === 'aws:upload_to_s3' ||
749 |         t.includes('upload') ||
750 |         t.includes('s3')
751 |       )).toBeTruthy();
752 |     });
753 | 
754 |     test('I need to create a serverless function', async () => {
755 |       const results = await engine.findRelevantTools('I need to create a serverless function', 5);
756 |       const topTools = results.map(r => r.name);
757 | 
758 |       expect(topTools.some(t =>
759 |         t === 'aws:create_lambda' ||
760 |         t.includes('lambda') ||
761 |         t.includes('function')
762 |       )).toBeTruthy();
763 |     });
764 |   });
765 | 
766 |   describe('File Operations User Stories', () => {
767 |     test('I need to read configuration file contents', async () => {
768 |       const results = await engine.findRelevantTools('I need to read configuration file contents', 5);
769 |       const topTools = results.map(r => r.name);
770 | 
771 |       expect(topTools.some(t =>
772 |         t === 'filesystem:read_file' ||
773 |         t.includes('read')
774 |       )).toBeTruthy();
775 |     });
776 | 
777 |     test('I want to backup important files', async () => {
778 |       const results = await engine.findRelevantTools('I want to backup important files', 5);
779 |       const topTools = results.map(r => r.name);
780 | 
781 |       expect(topTools.some(t =>
782 |         t === 'filesystem:copy_file' ||
783 |         t.includes('copy') ||
784 |         t.includes('backup')
785 |       )).toBeTruthy();
786 |     });
787 | 
788 |     test('I need to organize files into folders', async () => {
789 |       const results = await engine.findRelevantTools('I need to organize files into folders', 5);
790 |       const topTools = results.map(r => r.name);
791 | 
792 |       expect(topTools.some(t =>
793 |         t === 'filesystem:create_directory' ||
794 |         t.includes('directory') ||
795 |         t.includes('folder')
796 |       )).toBeTruthy();
797 |     });
798 | 
799 |     test('I want to delete old temporary files', async () => {
800 |       const results = await engine.findRelevantTools('I want to delete old temporary files', 5);
801 |       const topTools = results.map(r => r.name);
802 | 
803 |       expect(topTools.some(t =>
804 |         t === 'filesystem:delete_file' ||
805 |         t.includes('delete') ||
806 |         t.includes('remove')
807 |       )).toBeTruthy();
808 |     });
809 |   });
810 | 
811 |   describe('Productivity User Stories', () => {
812 |     test('I want to create documentation for my project', async () => {
813 |       const results = await engine.findRelevantTools('I want to create documentation for my project', 5);
814 |       const topTools = results.map(r => r.name);
815 | 
816 |       expect(topTools.some(t =>
817 |         t === 'notion:create_page' ||
818 |         t.includes('create') ||
819 |         t.includes('page')
820 |       )).toBeTruthy();
821 |     });
822 | 
823 |     test('I need to search for project information', async () => {
824 |       const results = await engine.findRelevantTools('I need to search for project information', 5);
825 |       const topTools = results.map(r => r.name);
826 | 
827 |       expect(topTools.some(t =>
828 |         t === 'notion:search_content' ||
829 |         t.includes('search')
830 |       )).toBeTruthy();
831 |     });
832 |   });
833 | 
834 |   describe('System Operations User Stories', () => {
835 |     test('I need to run a deployment script', async () => {
836 |       const results = await engine.findRelevantTools('I need to run a deployment script', 5);
837 |       const topTools = results.map(r => r.name);
838 | 
839 |       expect(topTools.some(t =>
840 |         t === 'shell:run_script' ||
841 |         t === 'shell:run_command' ||
842 |         t.includes('run') ||
843 |         t.includes('script')
844 |       )).toBeTruthy();
845 |     });
846 | 
847 |     test('I want to execute system commands', async () => {
848 |       const results = await engine.findRelevantTools('I want to execute system commands', 5);
849 |       const topTools = results.map(r => r.name);
850 | 
851 |       expect(topTools.some(t =>
852 |         t === 'shell:run_command' ||
853 |         t.includes('command') ||
854 |         t.includes('execute')
855 |       )).toBeTruthy();
856 |     });
857 |   });
858 | 
859 |   describe('Ecosystem Statistics', () => {
860 |     test('Domain analyzer should identify major categories', () => {
861 |       const stats = domainAnalyzer.getEcosystemStats();
862 | 
863 |       expect(stats.totalMCPs).toBeGreaterThan(30);
864 |       expect(stats.categories).toBeGreaterThan(8);
865 |       expect(stats.categoriesList).toContain('database');
866 |       expect(stats.categoriesList).toContain('developer-tools');
867 |       expect(stats.categoriesList).toContain('financial');
868 |       expect(parseFloat(stats.averagePopularity)).toBeGreaterThan(70);
869 |     });
870 | 
871 |     test('Enhancement data should be comprehensive', () => {
872 |       const enhancementData = domainAnalyzer.generateEnhancementData();
873 | 
874 |       expect(enhancementData.stats.domains).toBeGreaterThan(8);
875 |       expect(enhancementData.stats.bridges).toBeGreaterThan(10);
876 |       expect(Object.keys(enhancementData.domainCapabilities)).toContain('database');
877 |       expect(Object.keys(enhancementData.semanticBridges)).toContain('save my changes');
878 |     });
879 | 
880 |     test('Test coverage should represent real MCP ecosystem', () => {
881 |       const categories = new Set(ECOSYSTEM_TEST_MCPS.map(mcp => mcp.category));
882 | 
883 |       expect(categories.has('database')).toBeTruthy();
884 |       expect(categories.has('financial')).toBeTruthy();
885 |       expect(categories.has('developer-tools')).toBeTruthy();
886 |       expect(categories.has('communication')).toBeTruthy();
887 |       expect(categories.has('web-automation')).toBeTruthy();
888 |       expect(categories.has('cloud-infrastructure')).toBeTruthy();
889 |       expect(categories.has('file-operations')).toBeTruthy();
890 | 
891 |       // Verify we have comprehensive tool coverage
892 |       const totalTools = ECOSYSTEM_TEST_MCPS.reduce((sum, mcp) => sum + mcp.tools.length, 0);
893 |       expect(totalTools).toBeGreaterThan(40);
894 |     });
895 |   });
896 | });
```

--------------------------------------------------------------------------------
/src/server/mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * NCP MCP Server - Clean 2-Method Architecture
   3 |  * Exposes exactly 2 methods: discover + execute
   4 |  */
   5 | 
   6 | import { NCPOrchestrator } from '../orchestrator/ncp-orchestrator.js';
   7 | import { logger } from '../utils/logger.js';
   8 | import { ToolSchemaParser, ParameterInfo } from '../services/tool-schema-parser.js';
   9 | import { ToolContextResolver } from '../services/tool-context-resolver.js';
  10 | import { ToolFinder } from '../services/tool-finder.js';
  11 | import { UsageTipsGenerator } from '../services/usage-tips-generator.js';
  12 | import { TextUtils } from '../utils/text-utils.js';
  13 | import chalk from 'chalk';
  14 | 
  15 | interface MCPRequest {
  16 |   jsonrpc: string;
  17 |   id: string | number;
  18 |   method: string;
  19 |   params?: any;
  20 | }
  21 | 
  22 | interface MCPResponse {
  23 |   jsonrpc: string;
  24 |   id: string | number | null;
  25 |   result?: any;
  26 |   error?: {
  27 |     code: number;
  28 |     message: string;
  29 |     data?: any;
  30 |   };
  31 | }
  32 | 
  33 | interface MCPTool {
  34 |   name: string;
  35 |   description: string;
  36 |   inputSchema: {
  37 |     type: string;
  38 |     properties: Record<string, any>;
  39 |     required?: string[];
  40 |   };
  41 | }
  42 | 
  43 | export class MCPServer {
  44 |   private orchestrator: NCPOrchestrator;
  45 |   private initializationPromise: Promise<void> | null = null;
  46 |   private isInitialized: boolean = false;
  47 |   private initializationProgress: { current: number; total: number; currentMCP: string } | null = null;
  48 | 
  49 |   constructor(profileName: string = 'default', showProgress: boolean = false, forceRetry: boolean = false) {
  50 |     // Profile-aware orchestrator using real MCP connections
  51 |     this.orchestrator = new NCPOrchestrator(profileName, showProgress, forceRetry);
  52 |   }
  53 | 
  54 |   async initialize(): Promise<void> {
  55 |     logger.info('Starting NCP MCP server');
  56 | 
  57 |     // Start initialization in the background, don't await it
  58 |     this.initializationPromise = this.orchestrator.initialize().then(() => {
  59 |       this.isInitialized = true;
  60 |       this.initializationProgress = null;
  61 |       logger.info('NCP MCP server indexing complete');
  62 |     }).catch((error) => {
  63 |       logger.error('Failed to initialize orchestrator:', error);
  64 |       this.isInitialized = true; // Mark as initialized even on error to unblock
  65 |       this.initializationProgress = null;
  66 |     });
  67 | 
  68 |     // Don't wait for indexing to complete - return immediately
  69 |     logger.info('NCP MCP server ready (indexing in background)');
  70 |   }
  71 | 
  72 |   /**
  73 |    * Wait for initialization to complete
  74 |    * Useful for CLI commands that need full indexing before proceeding
  75 |    */
  76 |   async waitForInitialization(): Promise<void> {
  77 |     if (this.isInitialized) {
  78 |       return;
  79 |     }
  80 | 
  81 |     if (this.initializationPromise) {
  82 |       await this.initializationPromise;
  83 |     }
  84 |   }
  85 | 
  86 |   async handleRequest(request: any): Promise<MCPResponse | undefined> {
  87 |     // Handle notifications (requests without id)
  88 |     if (!('id' in request)) {
  89 |       // Handle common MCP notifications
  90 |       if (request.method === 'notifications/initialized') {
  91 |         // Client finished initialization - no response needed
  92 |         return undefined;
  93 |       }
  94 |       return undefined;
  95 |     }
  96 | 
  97 |     // Validate JSON-RPC structure
  98 |     if (!request || request.jsonrpc !== '2.0' || !request.method) {
  99 |       return {
 100 |         jsonrpc: '2.0',
 101 |         id: request.id || null,
 102 |         error: {
 103 |           code: -32600,
 104 |           message: 'Invalid request'
 105 |         }
 106 |       };
 107 |     }
 108 | 
 109 |     try {
 110 |       switch (request.method) {
 111 |         case 'initialize':
 112 |           return this.handleInitialize(request);
 113 | 
 114 |         case 'tools/list':
 115 |           return this.handleListTools(request);
 116 | 
 117 |         case 'tools/call':
 118 |           return this.handleCallTool(request);
 119 | 
 120 |         case 'prompts/list':
 121 |           return this.handleListPrompts(request);
 122 | 
 123 |         case 'resources/list':
 124 |           return this.handleListResources(request);
 125 | 
 126 |         default:
 127 |           return {
 128 |             jsonrpc: '2.0',
 129 |             id: request.id,
 130 |             error: {
 131 |               code: -32601,
 132 |               message: `Method not found: ${request.method}`
 133 |             }
 134 |           };
 135 |       }
 136 |     } catch (error: any) {
 137 |       logger.error(`Error handling request: ${error.message}`);
 138 |       return {
 139 |         jsonrpc: '2.0',
 140 |         id: request.id,
 141 |         error: {
 142 |           code: -32603,
 143 |           message: 'Internal error',
 144 |           data: error.message
 145 |         }
 146 |       };
 147 |     }
 148 |   }
 149 | 
 150 |   private handleInitialize(request: MCPRequest): MCPResponse {
 151 |     return {
 152 |       jsonrpc: '2.0',
 153 |       id: request.id,
 154 |       result: {
 155 |         protocolVersion: '2024-11-05',
 156 |         capabilities: {
 157 |           tools: {}
 158 |         },
 159 |         serverInfo: {
 160 |           name: 'ncp',
 161 |           title: 'Natural Context Provider - Unified MCP Orchestrator',
 162 |           version: '1.0.4'
 163 |         }
 164 |       }
 165 |     };
 166 |   }
 167 | 
 168 |   private async handleListTools(request: MCPRequest): Promise<MCPResponse> {
 169 |     // Always return tools immediately, even if indexing is in progress
 170 |     // This prevents MCP connection failures during startup
 171 |     const tools: MCPTool[] = [
 172 |       {
 173 |         name: 'find',
 174 |         description: 'Dual-mode tool discovery: (1) SEARCH MODE: Use with description parameter for intelligent vector search - describe your task as user story for best results: "I want to save configuration to a file", "I need to analyze logs for errors". (2) LISTING MODE: Call without description parameter for paginated browsing of all available MCPs and tools with depth control (0=tool names only, 1=tool names + descriptions, 2=full details with parameters).',
 175 |         inputSchema: {
 176 |           type: 'object',
 177 |           properties: {
 178 |             description: {
 179 |               type: 'string',
 180 |               description: 'SEARCH MODE: Search query as user story ("I want to save a file") or MCP name to filter results. LISTING MODE: Omit this parameter entirely to browse all available MCPs and tools with pagination.'
 181 |             },
 182 |             limit: {
 183 |               type: 'number',
 184 |               description: 'Maximum number of tools to return per page (default: 5 for search, 20 for list). Use higher values to see more results at once.'
 185 |             },
 186 |             page: {
 187 |               type: 'number',
 188 |               description: 'Page number for pagination (default: 1). Increment to see more results when total results exceed limit.'
 189 |             },
 190 |             confidence_threshold: {
 191 |               type: 'number',
 192 |               description: 'Minimum confidence level for search results (0.0-1.0, default: 0.3). Examples: 0.1=show all, 0.3=balanced, 0.5=strict, 0.7=very precise. Lower values show more loosely related tools, higher values show only close matches.'
 193 |             },
 194 |             depth: {
 195 |               type: 'number',
 196 |               description: 'Information depth level: 0=Tool names only, 1=Tool names + descriptions, 2=Full details with parameters (default, recommended for AI). Higher depth shows more complete information.',
 197 |               enum: [0, 1, 2],
 198 |               default: 2
 199 |             }
 200 |           }
 201 |         }
 202 |       },
 203 |       {
 204 |         name: 'run',
 205 |         description: 'Execute tools from managed MCP servers. Requires exact format "mcp_name:tool_name" with required parameters. System provides suggestions if tool not found and automatic fallbacks when tools fail.',
 206 |         inputSchema: {
 207 |           type: 'object',
 208 |           properties: {
 209 |             tool: {
 210 |               type: 'string',
 211 |               description: 'Tool to execute. Format: "mcp_name:tool_name"'
 212 |             },
 213 |             parameters: {
 214 |               type: 'object',
 215 |               description: 'Parameters to pass to the tool'
 216 |             },
 217 |             dry_run: {
 218 |               type: 'boolean',
 219 |               description: 'Preview what the tool will do without actually executing it (default: false)'
 220 |             }
 221 |           },
 222 |           required: ['tool']
 223 |         }
 224 |       }
 225 |     ];
 226 | 
 227 |     return {
 228 |       jsonrpc: '2.0',
 229 |       id: request.id,
 230 |       result: {
 231 |         tools
 232 |       }
 233 |     };
 234 |   }
 235 | 
 236 |   private async handleCallTool(request: MCPRequest): Promise<MCPResponse> {
 237 |     if (!request.params || !request.params.name) {
 238 |       return {
 239 |         jsonrpc: '2.0',
 240 |         id: request.id,
 241 |         error: {
 242 |           code: -32602,
 243 |           message: 'Invalid params: missing tool name'
 244 |         }
 245 |       };
 246 |     }
 247 | 
 248 |     const { name, arguments: args } = request.params;
 249 | 
 250 |     try {
 251 |       switch (name) {
 252 |         case 'find':
 253 |           return this.handleFind(request, args);
 254 | 
 255 |         case 'run':
 256 |           return this.handleRun(request, args);
 257 | 
 258 |         default:
 259 |           // Suggest similar methods
 260 |           const suggestions = this.getSuggestions(name, ['find', 'run']);
 261 |           const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(', ')}?` : '';
 262 | 
 263 |           return {
 264 |             jsonrpc: '2.0',
 265 |             id: request.id,
 266 |             error: {
 267 |               code: -32601,
 268 |               message: `Method not found: '${name}'. NCP OSS supports 'find' and 'run' methods.${suggestionText} Use 'find()' to discover available tools.`
 269 |             }
 270 |           };
 271 |       }
 272 |     } catch (error: any) {
 273 |       return {
 274 |         jsonrpc: '2.0',
 275 |         id: request.id,
 276 |         error: {
 277 |           code: -32603,
 278 |           message: error.message || 'Internal error'
 279 |         }
 280 |       };
 281 |     }
 282 |   }
 283 | 
 284 |   public async handleFind(request: MCPRequest, args: any): Promise<MCPResponse> {
 285 |     const isStillIndexing = !this.isInitialized && this.initializationPromise;
 286 | 
 287 |     const description = args?.description || '';
 288 |     const page = Math.max(1, args?.page || 1);
 289 |     const limit = args?.limit || (description ? 5 : 20);
 290 |     const depth = args?.depth !== undefined ? Math.max(0, Math.min(2, args.depth)) : 2;
 291 | 
 292 |     // Use ToolFinder service for search logic - always run to get partial results
 293 |     const finder = new ToolFinder(this.orchestrator);
 294 |     const findResult = await finder.find({
 295 |       query: description,
 296 |       page,
 297 |       limit,
 298 |       depth
 299 |     });
 300 | 
 301 |     const { tools: results, groupedByMCP: mcpGroups, pagination, mcpFilter, isListing } = findResult;
 302 | 
 303 |     // Get indexing progress if still indexing
 304 |     const progress = isStillIndexing ? this.orchestrator.getIndexingProgress() : null;
 305 | 
 306 |     const filterText = mcpFilter ? ` (filtered to ${mcpFilter})` : '';
 307 | 
 308 |     // Enhanced pagination display
 309 |     const paginationInfo = pagination.totalPages > 1 ?
 310 |       ` | Page ${pagination.page} of ${pagination.totalPages} (showing ${pagination.resultsInPage} of ${pagination.totalResults} results)` :
 311 |       ` (${pagination.totalResults} results)`;
 312 | 
 313 |     let output: string;
 314 |     if (description) {
 315 |       // Search mode - highlight the search query with reverse colors for emphasis
 316 |       const highlightedQuery = chalk.inverse(` ${description} `);
 317 |       output = `\n🔍 Found tools for ${highlightedQuery}${filterText}${paginationInfo}:\n\n`;
 318 |     } else {
 319 |       // Listing mode - show all available tools
 320 |       output = `\n🔍 Available tools${filterText}${paginationInfo}:\n\n`;
 321 |     }
 322 | 
 323 |     // Add MCP health status summary
 324 |     const healthStatus = this.orchestrator.getMCPHealthStatus();
 325 |     if (healthStatus.total > 0) {
 326 |       const healthIcon = healthStatus.unhealthy > 0 ? '⚠️' : '✅';
 327 |       output += `${healthIcon} **MCPs**: ${healthStatus.healthy}/${healthStatus.total} healthy`;
 328 | 
 329 |       if (healthStatus.unhealthy > 0) {
 330 |         const unhealthyNames = healthStatus.mcps
 331 |           .filter(mcp => !mcp.healthy)
 332 |           .map(mcp => mcp.name)
 333 |           .join(', ');
 334 |         output += ` (${unhealthyNames} unavailable)`;
 335 |       }
 336 |       output += '\n\n';
 337 |     }
 338 | 
 339 |     // Add indexing progress if still indexing (parity with CLI)
 340 |     if (progress && progress.total > 0) {
 341 |       const percentComplete = Math.round((progress.current / progress.total) * 100);
 342 |       const remainingTime = progress.estimatedTimeRemaining ?
 343 |         ` (~${Math.ceil(progress.estimatedTimeRemaining / 1000)}s remaining)` : '';
 344 | 
 345 |       output += `⏳ **Indexing in progress**: ${progress.current}/${progress.total} MCPs (${percentComplete}%)${remainingTime}\n`;
 346 |       output += `   Currently indexing: ${progress.currentMCP || 'initializing...'}\n\n`;
 347 | 
 348 |       if (results.length > 0) {
 349 |         output += `📋 **Showing partial results** - more tools will become available as indexing completes.\n\n`;
 350 |       } else {
 351 |         output += `📋 **No tools available yet** - please try again in a moment as indexing progresses.\n\n`;
 352 |       }
 353 |     }
 354 | 
 355 |     // Handle no results case (but only if not indexing - during indexing we already showed message above)
 356 |     if (results.length === 0 && !progress) {
 357 |       output += `❌ No tools found for "${description}"\n\n`;
 358 | 
 359 |       // Show sample of available MCPs
 360 |       const samples = await finder.getSampleTools(8);
 361 | 
 362 |       if (samples.length > 0) {
 363 |         output += `📝 Available MCPs to explore:\n`;
 364 |         samples.forEach(sample => {
 365 |           output += `📁 **${sample.mcpName}** - ${sample.description}\n`;
 366 |         });
 367 |         output += `\n💡 *Try broader search terms or specify an MCP name in your query.*`;
 368 |       }
 369 | 
 370 |       return {
 371 |         jsonrpc: '2.0',
 372 |         id: request.id,
 373 |         result: {
 374 |           content: [{ type: 'text', text: output }]
 375 |         }
 376 |       };
 377 |     }
 378 | 
 379 |     // If no results but still indexing, return progress message
 380 |     if (results.length === 0 && progress) {
 381 |       return {
 382 |         jsonrpc: '2.0',
 383 |         id: request.id,
 384 |         result: {
 385 |           content: [{ type: 'text', text: output }]
 386 |         }
 387 |       };
 388 |     }
 389 | 
 390 |     // Format output based on depth and mode
 391 |     if (depth === 0) {
 392 |       // Depth 0: Tool names only (no parameters, no descriptions)
 393 |       // Use original results array to maintain confidence-based ordering
 394 |       results.forEach((tool) => {
 395 |         if (isListing) {
 396 |           output += `# **${tool.toolName}**\n`;
 397 |         } else {
 398 |           const confidence = Math.round(tool.confidence * 100);
 399 |           output += `# **${tool.toolName}** (${confidence}% match)\n`;
 400 |         }
 401 |       });
 402 |     } else if (depth === 1) {
 403 |       // Depth 1: Tool name + description only (no parameters)
 404 |       // Use original results array to maintain confidence-based ordering
 405 |       results.forEach((tool, toolIndex) => {
 406 |         if (toolIndex > 0) output += '---\n';
 407 | 
 408 |         // Tool name
 409 |         if (isListing) {
 410 |           output += `# **${tool.toolName}**\n`;
 411 |         } else {
 412 |           const confidence = Math.round(tool.confidence * 100);
 413 |           output += `# **${tool.toolName}** (${confidence}% match)\n`;
 414 |         }
 415 | 
 416 |           // Tool description
 417 |         if (tool.description) {
 418 |           const cleanDescription = tool.description
 419 |             .replace(/^[^:]+:\s*/, '') // Remove MCP prefix
 420 |             .replace(/\s+/g, ' ') // Normalize whitespace
 421 |             .trim();
 422 |           output += `${cleanDescription}\n`;
 423 |         }
 424 | 
 425 |         // No parameters at depth 1
 426 |       });
 427 |     } else {
 428 |       // Depth 2: Full details with parameter descriptions
 429 |       // Use original results array to maintain confidence-based ordering
 430 |       results.forEach((tool, toolIndex) => {
 431 |         if (toolIndex > 0) output += '---\n';
 432 | 
 433 |         // Tool name
 434 |         if (isListing) {
 435 |           output += `# **${tool.toolName}**\n`;
 436 |         } else {
 437 |           const confidence = Math.round(tool.confidence * 100);
 438 |           output += `# **${tool.toolName}** (${confidence}% match)\n`;
 439 |         }
 440 | 
 441 |           // Tool description
 442 |         if (tool.description) {
 443 |           const cleanDescription = tool.description
 444 |             .replace(/^[^:]+:\s*/, '') // Remove MCP prefix
 445 |             .replace(/\s+/g, ' ') // Normalize whitespace
 446 |             .trim();
 447 |           output += `${cleanDescription}\n`;
 448 |         }
 449 | 
 450 |         // Parameters with descriptions inline
 451 |         if (tool.schema) {
 452 |           const params = this.parseParameters(tool.schema);
 453 |           if (params.length > 0) {
 454 |             params.forEach(param => {
 455 |               const optionalText = param.required ? '' : ' *(optional)*';
 456 |               const descText = param.description ? ` - ${param.description}` : '';
 457 |               output += `### ${param.name}: ${param.type}${optionalText}${descText}\n`;
 458 |             });
 459 |           } else {
 460 |             output += `*[no parameters]*\n`;
 461 |           }
 462 |         } else {
 463 |           output += `*[no parameters]*\n`;
 464 |         }
 465 |       });
 466 |     }
 467 | 
 468 |     // Add comprehensive usage guidance
 469 |     output += await UsageTipsGenerator.generate({
 470 |       depth,
 471 |       page: pagination.page,
 472 |       totalPages: pagination.totalPages,
 473 |       limit,
 474 |       totalResults: pagination.totalResults,
 475 |       description,
 476 |       mcpFilter,
 477 |       results
 478 |     });
 479 | 
 480 |     return {
 481 |       jsonrpc: '2.0',
 482 |       id: request.id,
 483 |       result: {
 484 |         content: [{
 485 |           type: 'text',
 486 |           text: output
 487 |         }]
 488 |       }
 489 |     };
 490 |   }
 491 | 
 492 | 
 493 | 
 494 |   private getToolContext(toolName: string): string {
 495 |     return ToolContextResolver.getContext(toolName);
 496 |   }
 497 | 
 498 |   private parseParameters(schema: any): ParameterInfo[] {
 499 |     return ToolSchemaParser.parseParameters(schema);
 500 |   }
 501 | 
 502 |   private wrapText(text: string, maxWidth: number, indent: string): string {
 503 |     return TextUtils.wrapText(text, {
 504 |       maxWidth,
 505 |       indent,
 506 |       cleanupPrefixes: true
 507 |     });
 508 |   }
 509 | 
 510 |   private getSuggestions(input: string, validOptions: string[]): string[] {
 511 |     const inputLower = input.toLowerCase();
 512 |     return validOptions.filter(option => {
 513 |       const optionLower = option.toLowerCase();
 514 |       // Simple fuzzy matching: check if input contains part of option or vice versa
 515 |       return optionLower.includes(inputLower) || inputLower.includes(optionLower) ||
 516 |              this.levenshteinDistance(inputLower, optionLower) <= 2;
 517 |     });
 518 |   }
 519 | 
 520 |   private levenshteinDistance(str1: string, str2: string): number {
 521 |     const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
 522 | 
 523 |     for (let i = 0; i <= str1.length; i += 1) {
 524 |       matrix[0][i] = i;
 525 |     }
 526 | 
 527 |     for (let j = 0; j <= str2.length; j += 1) {
 528 |       matrix[j][0] = j;
 529 |     }
 530 | 
 531 |     for (let j = 1; j <= str2.length; j += 1) {
 532 |       for (let i = 1; i <= str1.length; i += 1) {
 533 |         const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
 534 |         matrix[j][i] = Math.min(
 535 |           matrix[j][i - 1] + 1, // deletion
 536 |           matrix[j - 1][i] + 1, // insertion
 537 |           matrix[j - 1][i - 1] + indicator, // substitution
 538 |         );
 539 |       }
 540 |     }
 541 | 
 542 |     return matrix[str2.length][str1.length];
 543 |   }
 544 | 
 545 |   private generateDryRunPreview(toolIdentifier: string, parameters: any): string {
 546 |     const parts = toolIdentifier.includes(':') ? toolIdentifier.split(':', 2) : ['unknown', toolIdentifier];
 547 |     const mcpName = parts[0];
 548 |     const toolName = parts[1];
 549 | 
 550 |     let preview = `🛠️  Tool: ${toolName}\n📁 MCP: ${mcpName}\n📋 Parameters:\n`;
 551 | 
 552 |     if (Object.keys(parameters).length === 0) {
 553 |       preview += '   (none)\n';
 554 |     } else {
 555 |       for (const [key, value] of Object.entries(parameters)) {
 556 |         preview += `   ${key}: ${JSON.stringify(value)}\n`;
 557 |       }
 558 |     }
 559 | 
 560 |     // Add operation-specific warnings and descriptions
 561 |     const warnings = this.getDryRunWarnings(toolName, parameters);
 562 |     if (warnings.length > 0) {
 563 |       preview += '\n⚠️  Warnings:\n';
 564 |       warnings.forEach(warning => preview += `   • ${warning}\n`);
 565 |     }
 566 | 
 567 |     const description = this.getDryRunDescription(toolName, parameters);
 568 |     if (description) {
 569 |       preview += `\n📖 This operation will: ${description}`;
 570 |     }
 571 | 
 572 |     return preview;
 573 |   }
 574 | 
 575 |   private getDryRunWarnings(toolName: string, parameters: any): string[] {
 576 |     const warnings: string[] = [];
 577 | 
 578 |     if (toolName.includes('write') || toolName.includes('create')) {
 579 |       warnings.push('This operation will modify files/data');
 580 |     }
 581 |     if (toolName.includes('delete') || toolName.includes('remove')) {
 582 |       warnings.push('This operation will permanently delete data');
 583 |     }
 584 |     if (toolName.includes('move') || toolName.includes('rename')) {
 585 |       warnings.push('This operation will move/rename files');
 586 |     }
 587 |     if (parameters.path && (parameters.path.includes('/') || parameters.path.includes('\\'))) {
 588 |       warnings.push('File system operation - check path permissions');
 589 |     }
 590 | 
 591 |     return warnings;
 592 |   }
 593 | 
 594 |   private getDryRunDescription(toolName: string, parameters: any): string {
 595 |     if (toolName === 'write_file' && parameters.path) {
 596 |       return `Create or overwrite file at: ${parameters.path}`;
 597 |     }
 598 |     if (toolName === 'read_file' && parameters.path) {
 599 |       return `Read contents of file: ${parameters.path}`;
 600 |     }
 601 |     if (toolName === 'create_directory' && parameters.path) {
 602 |       return `Create directory at: ${parameters.path}`;
 603 |     }
 604 |     if (toolName === 'list_directory' && parameters.path) {
 605 |       return `List contents of directory: ${parameters.path}`;
 606 |     }
 607 | 
 608 |     return `Execute ${toolName} with provided parameters`;
 609 |   }
 610 | 
 611 |   private async handleRun(request: MCPRequest, args: any): Promise<MCPResponse> {
 612 |     // Check if indexing is still in progress
 613 |     if (!this.isInitialized && this.initializationPromise) {
 614 |       const progress = this.orchestrator.getIndexingProgress();
 615 | 
 616 |       if (progress && progress.total > 0) {
 617 |         const percentComplete = Math.round((progress.current / progress.total) * 100);
 618 |         const remainingTime = progress.estimatedTimeRemaining ?
 619 |           ` (~${Math.ceil(progress.estimatedTimeRemaining / 1000)}s remaining)` : '';
 620 | 
 621 |         const progressMessage = `⏳ **Indexing in progress**: ${progress.current}/${progress.total} MCPs (${percentComplete}%)${remainingTime}\n` +
 622 |           `Currently indexing: ${progress.currentMCP || 'initializing...'}\n\n` +
 623 |           `Tool execution will be available once indexing completes. Please try again in a moment.`;
 624 | 
 625 |         return {
 626 |           jsonrpc: '2.0',
 627 |           id: request.id,
 628 |           result: {
 629 |             content: [{ type: 'text', text: progressMessage }]
 630 |           }
 631 |         };
 632 |       }
 633 | 
 634 |       // Wait briefly for initialization to complete (max 2 seconds)
 635 |       try {
 636 |         let timeoutId: NodeJS.Timeout;
 637 |         await Promise.race([
 638 |           this.initializationPromise,
 639 |           new Promise((_, reject) => {
 640 |             timeoutId = setTimeout(() => reject(new Error('timeout')), 2000);
 641 |           })
 642 |         ]).finally(() => {
 643 |           if (timeoutId) clearTimeout(timeoutId);
 644 |         });
 645 |       } catch {
 646 |         // Continue even if timeout - try to execute with what's available
 647 |       }
 648 |     }
 649 | 
 650 |     if (!args?.tool) {
 651 |       return {
 652 |         jsonrpc: '2.0',
 653 |         id: request.id,
 654 |         error: {
 655 |           code: -32602,
 656 |           message: 'tool parameter is required'
 657 |         }
 658 |       };
 659 |     }
 660 | 
 661 |     const toolIdentifier = args.tool;
 662 |     const parameters = args.parameters || {};
 663 |     const dryRun = args.dry_run || false;
 664 | 
 665 |     // Extract _meta for transparent passthrough (session_id, etc.)
 666 |     const meta = request.params?._meta;
 667 | 
 668 |     if (dryRun) {
 669 |       // Dry run mode - show what would happen without executing
 670 |       const previewText = this.generateDryRunPreview(toolIdentifier, parameters);
 671 |       return {
 672 |         jsonrpc: '2.0',
 673 |         id: request.id,
 674 |         result: {
 675 |           content: [{
 676 |             type: 'text',
 677 |             text: `🔍 DRY RUN PREVIEW:\n\n${previewText}\n\n⚠️  This was a preview only. Set dry_run: false to execute.`
 678 |           }]
 679 |         }
 680 |       };
 681 |     }
 682 | 
 683 |     // Normal execution - pass _meta transparently
 684 |     const result = await this.orchestrator.run(toolIdentifier, parameters, meta);
 685 | 
 686 |     if (result.success) {
 687 |       return {
 688 |         jsonrpc: '2.0',
 689 |         id: request.id,
 690 |         result: {
 691 |           content: [{
 692 |             type: 'text',
 693 |             text: typeof result.content === 'string' ? result.content : JSON.stringify(result.content, null, 2)
 694 |           }]
 695 |         }
 696 |       };
 697 |     } else {
 698 |       return {
 699 |         jsonrpc: '2.0',
 700 |         id: request.id,
 701 |         error: {
 702 |           code: -32603,
 703 |           message: result.error || 'Tool execution failed'
 704 |         }
 705 |       };
 706 |     }
 707 |   }
 708 | 
 709 |   private async handleListPrompts(request: MCPRequest): Promise<MCPResponse> {
 710 |     try {
 711 |       const prompts = await this.orchestrator.getAllPrompts();
 712 |       return {
 713 |         jsonrpc: '2.0',
 714 |         id: request.id,
 715 |         result: {
 716 |           prompts: prompts || []
 717 |         }
 718 |       };
 719 |     } catch (error: any) {
 720 |       logger.error(`Error listing prompts: ${error.message}`);
 721 |       return {
 722 |         jsonrpc: '2.0',
 723 |         id: request.id,
 724 |         result: {
 725 |           prompts: []
 726 |         }
 727 |       };
 728 |     }
 729 |   }
 730 | 
 731 |   private async handleListResources(request: MCPRequest): Promise<MCPResponse> {
 732 |     try {
 733 |       const resources = await this.orchestrator.getAllResources();
 734 |       return {
 735 |         jsonrpc: '2.0',
 736 |         id: request.id,
 737 |         result: {
 738 |           resources: resources || []
 739 |         }
 740 |       };
 741 |     } catch (error: any) {
 742 |       logger.error(`Error listing resources: ${error.message}`);
 743 |       return {
 744 |         jsonrpc: '2.0',
 745 |         id: request.id,
 746 |         result: {
 747 |           resources: []
 748 |         }
 749 |       };
 750 |     }
 751 |   }
 752 | 
 753 |   async cleanup(): Promise<void> {
 754 |     await this.shutdown();
 755 |   }
 756 | 
 757 |   async shutdown(): Promise<void> {
 758 |     try {
 759 |       await this.orchestrator.cleanup();
 760 |       logger.info('NCP MCP server shut down gracefully');
 761 |     } catch (error: any) {
 762 |       logger.error(`Error during shutdown: ${error.message}`);
 763 |     }
 764 |   }
 765 | 
 766 |   /**
 767 |    * Set up stdio transport listener for MCP protocol messages.
 768 |    * Safe to call multiple times (idempotent).
 769 |    *
 770 |    * This should be called immediately when the process starts to ensure
 771 |    * the server is ready to receive protocol messages from any MCP client,
 772 |    * without requiring an explicit run() call.
 773 |    */
 774 |   startStdioListener(): void {
 775 |     // Prevent duplicate listener setup
 776 |     if ((this as any)._stdioListenerActive) {
 777 |       return;
 778 |     }
 779 |     (this as any)._stdioListenerActive = true;
 780 | 
 781 |     // Simple STDIO server
 782 |     process.stdin.setEncoding('utf8');
 783 |     let buffer = '';
 784 | 
 785 |     process.stdin.on('data', async (chunk) => {
 786 |       buffer += chunk;
 787 |       const lines = buffer.split('\n');
 788 |       buffer = lines.pop() || '';
 789 | 
 790 |       for (const line of lines) {
 791 |         if (line.trim()) {
 792 |           try {
 793 |             const request = JSON.parse(line);
 794 |             const response = await this.handleRequest(request);
 795 |             if (response) {
 796 |               process.stdout.write(JSON.stringify(response) + '\n');
 797 |             }
 798 |           } catch (error) {
 799 |             const errorResponse = {
 800 |               jsonrpc: '2.0',
 801 |               id: null,
 802 |               error: {
 803 |                 code: -32700,
 804 |                 message: 'Parse error'
 805 |               }
 806 |             };
 807 |             process.stdout.write(JSON.stringify(errorResponse) + '\n');
 808 |           }
 809 |         }
 810 |       }
 811 |     });
 812 | 
 813 |     process.stdin.on('end', () => {
 814 |       this.shutdown();
 815 |     });
 816 |   }
 817 | 
 818 |   /**
 819 |    * Legacy run() method for backwards compatibility.
 820 |    * Used by command-line interface entry point.
 821 |    *
 822 |    * For MCP server usage, prefer calling startStdioListener() immediately
 823 |    * and initialize() separately to be protocol-compliant.
 824 |    */
 825 |   async run(): Promise<void> {
 826 |     await this.initialize();
 827 |     this.startStdioListener();
 828 |   }
 829 | }
 830 | 
 831 | export class ParameterPredictor {
 832 |   predictValue(paramName: string, paramType: string, toolContext: string, description?: string, toolName?: string): any {
 833 |     const name = paramName.toLowerCase();
 834 |     const desc = (description || '').toLowerCase();
 835 |     const tool = (toolName || '').toLowerCase();
 836 | 
 837 |     // String type predictions
 838 |     if (paramType === 'string') {
 839 |       return this.predictStringValue(name, desc, toolContext, tool);
 840 |     }
 841 | 
 842 |     // Number type predictions
 843 |     if (paramType === 'number' || paramType === 'integer') {
 844 |       return this.predictNumberValue(name, desc, toolContext);
 845 |     }
 846 | 
 847 |     // Boolean type predictions
 848 |     if (paramType === 'boolean') {
 849 |       return this.predictBooleanValue(name, desc);
 850 |     }
 851 | 
 852 |     // Array type predictions
 853 |     if (paramType === 'array') {
 854 |       return this.predictArrayValue(name, desc, toolContext);
 855 |     }
 856 | 
 857 |     // Object type predictions
 858 |     if (paramType === 'object') {
 859 |       return this.predictObjectValue(name, desc);
 860 |     }
 861 | 
 862 |     // Default fallback
 863 |     return this.getDefaultForType(paramType);
 864 |   }
 865 | 
 866 |   private predictStringValue(name: string, desc: string, context: string, tool?: string): string {
 867 |     // File and path patterns
 868 |     if (name.includes('path') || name.includes('file') || desc.includes('path') || desc.includes('file')) {
 869 |       // Check if tool name suggests directory operations
 870 |       const isDirectoryTool = tool && (
 871 |         tool.includes('list_dir') ||
 872 |         tool.includes('list_folder') ||
 873 |         tool.includes('read_dir') ||
 874 |         tool.includes('scan_dir') ||
 875 |         tool.includes('get_dir')
 876 |       );
 877 | 
 878 |       // Check if parameter or description suggests directory
 879 |       const isDirectoryParam = name.includes('dir') ||
 880 |                               name.includes('folder') ||
 881 |                               desc.includes('directory') ||
 882 |                               desc.includes('folder');
 883 | 
 884 |       // Smart detection: if it's just "path" but tool is clearly for directories
 885 |       if (name === 'path' && isDirectoryTool) {
 886 |         return context === 'filesystem' ? '/home/user/documents' : './';
 887 |       }
 888 | 
 889 |       if (context === 'filesystem') {
 890 |         if (isDirectoryParam || isDirectoryTool) {
 891 |           return '/home/user/documents';
 892 |         }
 893 |         if (name.includes('config') || desc.includes('config')) {
 894 |           return '/etc/config.json';
 895 |         }
 896 |         return '/home/user/document.txt';
 897 |       }
 898 | 
 899 |       // Default based on whether it's likely a directory or file
 900 |       if (isDirectoryParam || isDirectoryTool) {
 901 |         return './';
 902 |       }
 903 |       return './file.txt';
 904 |     }
 905 | 
 906 |     // URL patterns
 907 |     if (name.includes('url') || name.includes('link') || desc.includes('url') || desc.includes('http')) {
 908 |       if (context === 'web') {
 909 |         return 'https://api.example.com/data';
 910 |       }
 911 |       return 'https://example.com';
 912 |     }
 913 | 
 914 |     // Email patterns
 915 |     if (name.includes('email') || name.includes('mail') || desc.includes('email')) {
 916 |       return '[email protected]';
 917 |     }
 918 | 
 919 |     // Name patterns
 920 |     if (name.includes('name') || name === 'title' || name === 'label') {
 921 |       if (context === 'filesystem') {
 922 |         return 'my-file';
 923 |       }
 924 |       return 'example-name';
 925 |     }
 926 | 
 927 |     // Content/text patterns
 928 |     if (name.includes('content') || name.includes('text') || name.includes('message') || name.includes('body')) {
 929 |       return 'Hello, world!';
 930 |     }
 931 | 
 932 |     // Query/search patterns
 933 |     if (name.includes('query') || name.includes('search') || name.includes('term')) {
 934 |       return 'search term';
 935 |     }
 936 | 
 937 |     // Key/ID patterns
 938 |     if (name.includes('key') || name.includes('id') || name.includes('token')) {
 939 |       if (context === 'payment') {
 940 |         return 'sk_test_...';
 941 |       }
 942 |       return 'abc123';
 943 |     }
 944 | 
 945 |     // Command patterns
 946 |     if (name.includes('command') || name.includes('cmd')) {
 947 |       if (context === 'system') {
 948 |         return 'ls -la';
 949 |       }
 950 |       return 'echo hello';
 951 |     }
 952 | 
 953 |     // Default string
 954 |     return 'example';
 955 |   }
 956 | 
 957 |   private predictNumberValue(name: string, desc: string, context: string): number {
 958 |     // Process ID patterns
 959 |     if (name.includes('pid') || desc.includes('process') || desc.includes('pid')) {
 960 |       return 1234;
 961 |     }
 962 | 
 963 |     // Port patterns
 964 |     if (name.includes('port') || desc.includes('port')) {
 965 |       return 8080;
 966 |     }
 967 | 
 968 |     // Size/length patterns
 969 |     if (name.includes('size') || name.includes('length') || name.includes('limit') || name.includes('count')) {
 970 |       return 10;
 971 |     }
 972 | 
 973 |     // Line number patterns
 974 |     if (name.includes('line') || name.includes('head') || name.includes('tail')) {
 975 |       return 5;
 976 |     }
 977 | 
 978 |     // Timeout patterns
 979 |     if (name.includes('timeout') || name.includes('delay') || desc.includes('timeout')) {
 980 |       return 5000;
 981 |     }
 982 | 
 983 |     // Default number
 984 |     return 1;
 985 |   }
 986 | 
 987 |   private predictBooleanValue(name: string, desc: string): boolean {
 988 |     // Negative patterns default to false
 989 |     if (name.includes('disable') || name.includes('skip') || name.includes('ignore')) {
 990 |       return false;
 991 |     }
 992 | 
 993 |     // Most booleans default to true for examples
 994 |     return true;
 995 |   }
 996 | 
 997 |   private predictArrayValue(name: string, desc: string, context: string): any[] {
 998 |     // File paths array
 999 |     if (name.includes('path') || name.includes('file') || desc.includes('path')) {
1000 |       return ['/path/to/file1.txt', '/path/to/file2.txt'];
1001 |     }
1002 | 
1003 |     // Arguments array
1004 |     if (name.includes('arg') || name.includes('param') || desc.includes('argument')) {
1005 |       return ['--verbose', '--output', 'result.txt'];
1006 |     }
1007 | 
1008 |     // Tags/keywords
1009 |     if (name.includes('tag') || name.includes('keyword') || name.includes('label')) {
1010 |       return ['tag1', 'tag2'];
1011 |     }
1012 | 
1013 |     // Default array
1014 |     return ['item1', 'item2'];
1015 |   }
1016 | 
1017 |   private predictObjectValue(name: string, desc: string): object {
1018 |     // Options/config object
1019 |     if (name.includes('option') || name.includes('config') || name.includes('setting')) {
1020 |       return { enabled: true, timeout: 5000 };
1021 |     }
1022 | 
1023 |     // Default object
1024 |     return { key: 'value' };
1025 |   }
1026 | 
1027 |   private getDefaultForType(type: string): any {
1028 |     switch (type) {
1029 |       case 'string': return 'value';
1030 |       case 'number':
1031 |       case 'integer': return 0;
1032 |       case 'boolean': return true;
1033 |       case 'array': return [];
1034 |       case 'object': return {};
1035 |       default: return null;
1036 |     }
1037 |   }
1038 | }
1039 | 
1040 | export default MCPServer;
```

--------------------------------------------------------------------------------
/src/discovery/rag-engine.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * Persistent RAG Engine for NCP
   3 |  * Uses transformer.js for embeddings with persistent caching
   4 |  */
   5 | 
   6 | import * as path from 'path';
   7 | import { getNcpBaseDirectory } from '../utils/ncp-paths.js';
   8 | import * as fs from 'fs/promises';
   9 | import * as crypto from 'crypto';
  10 | import { existsSync, mkdirSync, statSync } from 'fs';
  11 | import { logger } from '../utils/logger.js';
  12 | import { SemanticEnhancementEngine } from './semantic-enhancement-engine.js';
  13 | import { version } from '../utils/version.js';
  14 | 
  15 | // Import transformer.js (will be added to dependencies)
  16 | declare const pipeline: any;
  17 | 
  18 | export interface ToolEmbedding {
  19 |   embedding: Float32Array;
  20 |   hash: string;
  21 |   lastUpdated: string;
  22 |   toolName: string;
  23 |   description: string;
  24 |   enhancedDescription?: string;
  25 |   mcpName?: string;
  26 |   mcpDomain?: string;
  27 | }
  28 | 
  29 | export interface CacheMetadata {
  30 |   version: string;
  31 |   createdAt: string;
  32 |   lastValidated: string;
  33 |   configHash: string;
  34 |   mcpHashes: Record<string, string>;
  35 |   totalTools: number;
  36 | }
  37 | 
  38 | export interface DiscoveryResult {
  39 |   toolId: string;
  40 |   confidence: number;
  41 |   reason: string;
  42 |   similarity: number;
  43 |   originalSimilarity?: number;
  44 |   domain?: string;
  45 | }
  46 | 
  47 | export class PersistentRAGEngine {
  48 |   
  49 |   /**
  50 |    * Get domain classification for an MCP to improve cross-domain disambiguation
  51 |    */
  52 |   private getMCPDomain(mcpName: string): string {
  53 |     const domainMappings: Record<string, string> = {
  54 |       // Web development and frontend
  55 |       'context7-mcp': 'web development documentation',
  56 |       'vscode-mcp': 'code editor',
  57 |       
  58 |       // Financial/payment services
  59 |       'stripe': 'payment processing financial',
  60 |       'paypal': 'payment processing financial',
  61 |       
  62 |       // File and system operations
  63 |       'desktop-commander': 'file system operations',
  64 |       'Shell': 'command line system',
  65 |       'filesystem': 'file system operations',
  66 |       
  67 |       // Development tools
  68 |       'portel': 'code analysis development',
  69 |       'git': 'version control development',
  70 |       'sequential-thinking': 'development workflow',
  71 |       
  72 |       // AI and search
  73 |       'tavily': 'web search information',
  74 |       'perplexity': 'web search information',
  75 |       'anthropic': 'AI language model',
  76 |       
  77 |       // Database and data
  78 |       'postgres': 'database operations',
  79 |       'sqlite': 'database operations',
  80 |       'mongodb': 'database operations',
  81 |       
  82 |       // Communication and social
  83 |       'slack': 'team communication',
  84 |       'email': 'email communication',
  85 |       
  86 |       // Cloud and infrastructure
  87 |       'aws': 'cloud infrastructure',
  88 |       'gcp': 'cloud infrastructure',
  89 |       'docker': 'containerization infrastructure',
  90 |     };
  91 |     
  92 |     return domainMappings[mcpName] || 'general utility';
  93 |   }
  94 |   
  95 |   /**
  96 |    * Infer likely domains from query text to improve cross-domain disambiguation
  97 |    */
  98 |   private inferQueryDomains(query: string): string[] {
  99 |     const domainKeywords: Record<string, string[]> = {
 100 |       'web development': ['react', 'vue', 'angular', 'javascript', 'typescript', 'frontend', 'web', 'html', 'css', 'component', 'jsx', 'tsx'],
 101 |       'payment processing': ['payment', 'stripe', 'paypal', 'billing', 'invoice', 'subscription', 'checkout', 'transaction'],
 102 |       'file system': ['file', 'directory', 'folder', 'path', 'move', 'copy', 'delete', 'create', 'read', 'write'],
 103 |       'command line': ['command', 'shell', 'bash', 'terminal', 'execute', 'run', 'script'],
 104 |       'database': ['database', 'sql', 'query', 'table', 'record', 'postgres', 'mysql', 'mongodb'],
 105 |       'cloud infrastructure': ['aws', 'gcp', 'azure', 'cloud', 'deploy', 'infrastructure', 'docker', 'kubernetes'],
 106 |       'development': ['code', 'development', 'debug', 'build', 'compile', 'test', 'git', 'version', 'repository'],
 107 |       'search': ['search', 'find', 'lookup', 'query', 'information', 'web search'],
 108 |       'communication': ['email', 'slack', 'message', 'send', 'notification', 'team']
 109 |     };
 110 |     
 111 |     const inferredDomains: string[] = [];
 112 |     
 113 |     for (const [domain, keywords] of Object.entries(domainKeywords)) {
 114 |       const matchCount = keywords.filter(keyword => query.includes(keyword)).length;
 115 |       if (matchCount > 0) {
 116 |         inferredDomains.push(domain);
 117 |       }
 118 |     }
 119 |     
 120 |     return inferredDomains;
 121 |   }
 122 | 
 123 |   /**
 124 |    * Add capability enhancements for reverse domain mapping
 125 |    * Terminal/shell tools should advertise their git, build, and development capabilities
 126 |    */
 127 |   private getCapabilityEnhancements(toolName: string, description: string): string {
 128 |     const enhancements: string[] = [];
 129 | 
 130 |     // Terminal/shell tools get comprehensive capability advertisements
 131 |     if (toolName.includes('start_process') ||
 132 |         toolName.includes('run_command') ||
 133 |         description.toLowerCase().includes('terminal') ||
 134 |         description.toLowerCase().includes('shell') ||
 135 |         description.toLowerCase().includes('command line') ||
 136 |         description.toLowerCase().includes('execute')) {
 137 | 
 138 |       enhancements.push(
 139 |         // Git capabilities
 140 |         ' Can execute git commands: git commit, git push, git pull, git status, git add, git log, git diff, git branch, git checkout, git merge, git clone.',
 141 |         // Development tool capabilities
 142 |         ' Can run development tools: npm, yarn, bun, pip, cargo, make, build scripts.',
 143 |         // System command capabilities
 144 |         ' Can execute system commands: ls, cd, mkdir, rm, cp, mv, chmod, chown.',
 145 |         // Package manager capabilities
 146 |         ' Can run package managers: apt, brew, yum, pacman.',
 147 |         // Script execution capabilities
 148 |         ' Can execute scripts: bash scripts, python scripts, shell scripts.',
 149 |         // Build and deployment capabilities
 150 |         ' Can run build tools: webpack, vite, rollup, parcel, docker, kubernetes.'
 151 |       );
 152 |     }
 153 | 
 154 |     // File management tools get development-related file capabilities
 155 |     if (toolName.includes('read_file') ||
 156 |         toolName.includes('write_file') ||
 157 |         toolName.includes('edit_file')) {
 158 | 
 159 |       enhancements.push(
 160 |         ' Can handle development files: package.json, tsconfig.json, .gitignore, README.md, configuration files.'
 161 |       );
 162 |     }
 163 | 
 164 |     return enhancements.join('');
 165 |   }
 166 | 
 167 |   private model: any;
 168 |   private vectorDB: Map<string, ToolEmbedding> = new Map();
 169 |   private dbPath: string;
 170 |   private metadataPath: string;
 171 |   private cacheMetadata: CacheMetadata | null = null;
 172 |   private isInitialized = false;
 173 |   private indexingQueue: Array<{ mcpName: string; tools: any[] }> = [];
 174 |   private isIndexing = false;
 175 |   private semanticEnhancementEngine: SemanticEnhancementEngine;
 176 | 
 177 |   constructor() {
 178 |     const ncpDir = getNcpBaseDirectory();
 179 |     this.dbPath = path.join(ncpDir, 'embeddings.json');
 180 |     this.metadataPath = path.join(ncpDir, 'embeddings-metadata.json');
 181 | 
 182 |     // Initialize semantic enhancement engine with industry-standard architecture
 183 |     this.semanticEnhancementEngine = new SemanticEnhancementEngine();
 184 | 
 185 |     this.ensureDirectoryExists(ncpDir);
 186 | 
 187 |     logger.info('RAG Engine initialized with Semantic Enhancement Engine');
 188 |     logger.debug(`Enhancement statistics: ${JSON.stringify(this.semanticEnhancementEngine.getEnhancementStatistics())}`);
 189 |   }
 190 | 
 191 |   /**
 192 |    * Validate cache against current configuration
 193 |    */
 194 |   async validateCache(currentConfig?: any): Promise<boolean> {
 195 |     try {
 196 |       if (!existsSync(this.dbPath) || !existsSync(this.metadataPath)) {
 197 |         logger.debug('🔍 Cache files missing, needs rebuild');
 198 |         return false;
 199 |       }
 200 | 
 201 |       // Load cache metadata
 202 |       const metadataContent = await fs.readFile(this.metadataPath, 'utf-8');
 203 |       this.cacheMetadata = JSON.parse(metadataContent);
 204 | 
 205 |       if (!this.cacheMetadata) {
 206 |         logger.debug('🔍 Cache metadata invalid, needs rebuild');
 207 |         return false;
 208 |       }
 209 | 
 210 |       // Check if cache is too old (older than 7 days)
 211 |       const cacheAge = Date.now() - new Date(this.cacheMetadata.createdAt).getTime();
 212 |       const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
 213 |       if (cacheAge > maxAge) {
 214 |         logger.info('🕐 Cache is older than 7 days, rebuilding for freshness');
 215 |         return false;
 216 |       }
 217 | 
 218 |       // If current config provided, validate against it
 219 |       if (currentConfig) {
 220 |         const currentConfigHash = this.hashObject(currentConfig);
 221 |         if (this.cacheMetadata.configHash !== currentConfigHash) {
 222 |           logger.info('🔄 Configuration changed, invalidating cache');
 223 |           return false;
 224 |         }
 225 |       }
 226 | 
 227 |       logger.debug('✅ Cache validation passed');
 228 |       return true;
 229 | 
 230 |     } catch (error) {
 231 |       logger.warn(`⚠️ Cache validation failed: ${error}`);
 232 |       return false;
 233 |     }
 234 |   }
 235 | 
 236 |   /**
 237 |    * Generate hash of configuration for change detection
 238 |    */
 239 |   private hashObject(obj: any): string {
 240 |     const str = JSON.stringify(obj, Object.keys(obj).sort());
 241 |     return crypto.createHash('sha256').update(str).digest('hex');
 242 |   }
 243 | 
 244 |   /**
 245 |    * Update cache metadata
 246 |    */
 247 |   private async updateCacheMetadata(mcpHashes: Record<string, string>): Promise<void> {
 248 |     this.cacheMetadata = {
 249 |       version,
 250 |       createdAt: new Date().toISOString(),
 251 |       lastValidated: new Date().toISOString(),
 252 |       configHash: '', // Will be set when config is available
 253 |       mcpHashes,
 254 |       totalTools: this.vectorDB.size
 255 |     };
 256 | 
 257 |     try {
 258 |       await fs.writeFile(this.metadataPath, JSON.stringify(this.cacheMetadata, null, 2));
 259 |       logger.debug('💾 Cache metadata updated');
 260 |     } catch (error) {
 261 |       logger.error(`❌ Failed to save cache metadata: ${error}`);
 262 |     }
 263 |   }
 264 | 
 265 |   /**
 266 |    * Initialize the RAG engine with embedding model
 267 |    * Falls back gracefully if transformer.js fails to load
 268 |    */
 269 |   async initialize(currentConfig?: any): Promise<void> {
 270 |     if (this.isInitialized) return;
 271 | 
 272 |     logger.info('🧠 Initializing RAG engine...');
 273 |     const startTime = Date.now();
 274 | 
 275 |     // Validate cache before proceeding
 276 |     const cacheValid = await this.validateCache(currentConfig);
 277 |     
 278 |     if (!cacheValid) {
 279 |       logger.info('🔄 Cache invalid, clearing and will rebuild on demand');
 280 |       await this.clearCache();
 281 |     }
 282 | 
 283 |     // Store original console.warn before try block
 284 |     const originalConsoleWarn = console.warn;
 285 | 
 286 |     try {
 287 |       // Configure transformers environment to suppress content-length warnings
 288 |       process.env.TRANSFORMERS_VERBOSITY = 'error';  // Suppress info/warning logs
 289 | 
 290 |       // Temporarily suppress the specific content-length warning
 291 |       console.warn = (...args: any[]) => {
 292 |         const message = args.join(' ');
 293 |         if (message.includes('Unable to determine content-length') ||
 294 |             message.includes('Will expand buffer when needed')) {
 295 |           return; // Suppress this specific warning
 296 |         }
 297 |         originalConsoleWarn.apply(console, args);
 298 |       };
 299 | 
 300 |       // Dynamically import transformer.js
 301 |       const { pipeline, env } = await import('@xenova/transformers');
 302 | 
 303 |       // Configure transformers to suppress download warnings
 304 |       env.allowLocalModels = false;
 305 |       env.allowRemoteModels = true;
 306 |       
 307 |       // Load sentence transformer model
 308 |       logger.info('📥 Loading embedding model (all-MiniLM-L6-v2)...');
 309 |       this.model = await pipeline(
 310 |         'feature-extraction', 
 311 |         'Xenova/all-MiniLM-L6-v2',
 312 |         { 
 313 |           quantized: true,  // Use quantized version for smaller size
 314 |           progress_callback: (progress: any) => {
 315 |             if (progress.status === 'downloading') {
 316 |               logger.info(`📥 Downloading model: ${Math.round(progress.progress)}%`);
 317 |             }
 318 |           }
 319 |         }
 320 |       );
 321 | 
 322 |       // Restore original console.warn after model loading
 323 |       console.warn = originalConsoleWarn;
 324 | 
 325 |       // Load cached embeddings (if cache was valid)
 326 |       if (cacheValid) {
 327 |         await this.loadPersistedEmbeddings();
 328 |       }
 329 | 
 330 |       const initTime = Date.now() - startTime;
 331 |       logger.info(`✅ RAG engine initialized in ${initTime}ms`);
 332 |       logger.info(`📊 Loaded ${this.vectorDB.size} cached embeddings`);
 333 | 
 334 |       this.isInitialized = true;
 335 | 
 336 |       // Process any queued indexing tasks
 337 |       this.processIndexingQueue();
 338 | 
 339 |     } catch (error) {
 340 |       // Restore original console.warn in case of error
 341 |       console.warn = originalConsoleWarn;
 342 | 
 343 |       logger.warn(`⚠️ RAG engine failed to initialize: ${error}`);
 344 |       logger.info('🔄 Falling back to keyword-based discovery');
 345 | 
 346 |       // Mark as initialized but without model (fallback mode)
 347 |       this.isInitialized = true;
 348 |       this.model = null;
 349 |       
 350 |       // Still load cached embeddings for basic functionality (if cache was valid)
 351 |       if (cacheValid) {
 352 |         try {
 353 |           await this.loadPersistedEmbeddings();
 354 |           logger.info(`📊 Loaded ${this.vectorDB.size} cached embeddings (fallback mode)`);
 355 |         } catch {
 356 |           // Ignore cache loading errors in fallback mode
 357 |         }
 358 |       }
 359 |       
 360 |       // Process any queued indexing tasks (will use fallback)
 361 |       this.processIndexingQueue();
 362 |     }
 363 |   }
 364 | 
 365 |   /**
 366 |    * Index tools from an MCP (progressive loading)
 367 |    */
 368 |   async indexMCP(mcpName: string, tools: any[]): Promise<void> {
 369 |     if (!this.isInitialized) {
 370 |       // Queue for later processing
 371 |       this.indexingQueue.push({ mcpName, tools });
 372 |       logger.info(`📋 Queued ${mcpName} for indexing (${tools.length} tools)`);
 373 |       return;
 374 |     }
 375 | 
 376 |     if (this.isIndexing) {
 377 |       // Add to queue if already indexing
 378 |       this.indexingQueue.push({ mcpName, tools });
 379 |       return;
 380 |     }
 381 | 
 382 |     await this.performIndexing(mcpName, tools);
 383 |   }
 384 | 
 385 |   /**
 386 |    * Fast indexing for startup - loads from embeddings cache if available
 387 |    * This is called during optimized cache loading to avoid regenerating embeddings
 388 |    */
 389 |   async indexMCPFromCache(mcpName: string, tools: any[]): Promise<void> {
 390 |     if (!this.isInitialized) {
 391 |       // Queue for later processing
 392 |       this.indexingQueue.push({ mcpName, tools });
 393 |       return;
 394 |     }
 395 | 
 396 |     // Fast path: check if all tools are already in vectorDB
 397 |     let allCached = true;
 398 |     for (const tool of tools) {
 399 |       const toolId = tool.id || `${mcpName}:${tool.name}`;
 400 |       if (!this.vectorDB.has(toolId)) {
 401 |         allCached = false;
 402 |         break;
 403 |       }
 404 |     }
 405 | 
 406 |     if (allCached) {
 407 |       logger.debug(`⚡ All ${tools.length} tools for ${mcpName} already cached`);
 408 |       return;
 409 |     }
 410 | 
 411 |     // Fallback to normal indexing if not all cached
 412 |     await this.performIndexing(mcpName, tools);
 413 |   }
 414 | 
 415 |   /**
 416 |    * Perform actual indexing of tools
 417 |    */
 418 |   private async performIndexing(mcpName: string, tools: any[]): Promise<void> {
 419 |     this.isIndexing = true;
 420 |     logger.info(`🔍 Indexing ${mcpName} (${tools.length} tools)...`);
 421 | 
 422 |     let newEmbeddings = 0;
 423 |     let cachedEmbeddings = 0;
 424 | 
 425 |     try {
 426 |       for (const tool of tools) {
 427 |         const toolId = tool.id || `${mcpName}:${tool.name}`;
 428 |         const description = tool.description || tool.name;
 429 |         const hash = this.hashDescription(description);
 430 |         
 431 |         const cached = this.vectorDB.get(toolId);
 432 |         
 433 |         // Skip if we already have this exact description
 434 |         if (cached && cached.hash === hash) {
 435 |           logger.debug(`💾 Using cached embedding for ${toolId}`);
 436 |           cachedEmbeddings++;
 437 |           continue;
 438 |         }
 439 | 
 440 |         // Generate new embedding (or skip in fallback mode)
 441 |         if (this.model) {
 442 |           logger.debug(`🧮 Computing embedding for ${toolId}...`);
 443 |           try {
 444 |             const mcpDomain = this.getMCPDomain(mcpName);
 445 |             const capabilityEnhancements = this.getCapabilityEnhancements(tool.name, description);
 446 |             // Include the tool identifier for exact searches: git:commit, filesystem:read_file, etc.
 447 |             const toolIdentifier = `${mcpName}:${tool.name}`;
 448 |             const enhancedDescription = `${toolIdentifier} ${mcpDomain} context: ${description}${capabilityEnhancements}`;
 449 |             
 450 |             const embedding = await this.model(enhancedDescription, { 
 451 |               pooling: 'mean', 
 452 |               normalize: true 
 453 |             });
 454 |             
 455 |             this.vectorDB.set(toolId, {
 456 |               embedding: new Float32Array(embedding.data),
 457 |               hash: hash,
 458 |               lastUpdated: new Date().toISOString(),
 459 |               toolName: tool.name,
 460 |               description: description,
 461 |               enhancedDescription: enhancedDescription,
 462 |               mcpName: mcpName,
 463 |               mcpDomain: mcpDomain
 464 |             });
 465 |             
 466 |             newEmbeddings++;
 467 |           } catch (error) {
 468 |             logger.error(`❌ Failed to compute embedding for ${toolId}: ${error}`);
 469 |           }
 470 |         } else {
 471 |           // In fallback mode, just store tool metadata without embeddings
 472 |           const mcpDomain = this.getMCPDomain(mcpName);
 473 |           this.vectorDB.set(toolId, {
 474 |             embedding: new Float32Array([]), // Empty embedding
 475 |             hash: hash,
 476 |             lastUpdated: new Date().toISOString(),
 477 |             toolName: tool.name,
 478 |             description: description,
 479 |             enhancedDescription: `${mcpDomain} context: ${description}${this.getCapabilityEnhancements(tool.name, description)}`,
 480 |             mcpName: mcpName,
 481 |             mcpDomain: mcpDomain
 482 |           });
 483 |           newEmbeddings++;
 484 |         }
 485 |       }
 486 | 
 487 |       // Update MCP hash for change detection
 488 |       const mcpHash = this.hashObject(tools);
 489 |       const mcpHashes = this.cacheMetadata?.mcpHashes || {};
 490 |       mcpHashes[mcpName] = mcpHash;
 491 | 
 492 |       // Persist to disk after each MCP
 493 |       await this.persistEmbeddings();
 494 |       await this.updateCacheMetadata(mcpHashes);
 495 | 
 496 |       logger.info(`✅ ${mcpName} indexed: ${newEmbeddings} new, ${cachedEmbeddings} cached`);
 497 | 
 498 |     } catch (error) {
 499 |       logger.error(`❌ Failed to index ${mcpName}: ${error}`);
 500 |     } finally {
 501 |       this.isIndexing = false;
 502 |       
 503 |       // Process next item in queue
 504 |       if (this.indexingQueue.length > 0) {
 505 |         const next = this.indexingQueue.shift()!;
 506 |         setImmediate(() => this.performIndexing(next.mcpName, next.tools));
 507 |       }
 508 |     }
 509 |   }
 510 | 
 511 |   /**
 512 |    * Process queued indexing tasks
 513 |    */
 514 |   private async processIndexingQueue(): Promise<void> {
 515 |     while (this.indexingQueue.length > 0) {
 516 |       const task = this.indexingQueue.shift()!;
 517 |       await this.performIndexing(task.mcpName, task.tools);
 518 |     }
 519 |   }
 520 | 
 521 |   /**
 522 |    * Discover tools using semantic similarity (or fallback to keyword matching)
 523 |    */
 524 |   async discover(query: string, maxResults = 5): Promise<DiscoveryResult[]> {
 525 |     if (!this.isInitialized) {
 526 |       logger.warn('⚠️ RAG engine not initialized, falling back to keyword matching');
 527 |       return this.fallbackKeywordSearch(query, maxResults);
 528 |     }
 529 | 
 530 |     if (this.vectorDB.size === 0) {
 531 |       logger.warn('⚠️ No embeddings available yet');
 532 |       return [];
 533 |     }
 534 | 
 535 |     // If no model available (fallback mode), use keyword search
 536 |     if (!this.model) {
 537 |       logger.debug(`🔍 Keyword discovery (fallback mode): "${query}"`);
 538 |       return this.fallbackKeywordSearch(query, maxResults);
 539 |     }
 540 | 
 541 |     try {
 542 |       logger.debug(`🔍 RAG discovery: "${query}"`);
 543 |       
 544 |       // Check if any tools have actual embeddings
 545 |       let toolsWithEmbeddings = 0;
 546 |       for (const [toolId, toolData] of this.vectorDB) {
 547 |         if (toolData.embedding.length > 0) {
 548 |           toolsWithEmbeddings++;
 549 |         }
 550 |       }
 551 |       
 552 |       logger.debug(`Tools with embeddings: ${toolsWithEmbeddings}/${this.vectorDB.size}`);
 553 |       
 554 |       // If no tools have embeddings, fall back to keyword search
 555 |       if (toolsWithEmbeddings === 0) {
 556 |         logger.debug('No tools have embeddings, falling back to keyword search');
 557 |         return this.fallbackKeywordSearch(query, maxResults);
 558 |       }
 559 |       
 560 |       // Generate query embedding
 561 |       const queryEmbedding = await this.model(query, { 
 562 |         pooling: 'mean', 
 563 |         normalize: true 
 564 |       });
 565 |       
 566 |       // Calculate similarities
 567 |       const similarities: Array<{ toolId: string; similarity: number }> = [];
 568 |       
 569 |       for (const [toolId, toolData] of this.vectorDB) {
 570 |         // Skip tools with empty embeddings (fallback mode entries)
 571 |         if (toolData.embedding.length === 0) {
 572 |           continue;
 573 |         }
 574 |         
 575 |         const similarity = this.cosineSimilarity(
 576 |           queryEmbedding.data,
 577 |           toolData.embedding
 578 |         );
 579 |         
 580 |         similarities.push({ toolId, similarity });
 581 |       }
 582 |       
 583 |       // Git-specific boosting: if query contains git terms, moderately boost Shell tools
 584 |       const queryLower = query.toLowerCase();
 585 |       const gitTerms = ['git', 'commit', 'push', 'pull', 'checkout', 'branch', 'merge', 'clone', 'status', 'log', 'diff', 'add', 'remote', 'fetch', 'rebase', 'stash', 'tag'];
 586 |       const hasGitTerms = gitTerms.some(term => queryLower.includes(term));
 587 | 
 588 |       if (hasGitTerms) {
 589 |         for (const result of similarities) {
 590 |           if (result.toolId.startsWith('Shell:')) {
 591 |             result.similarity = Math.min(0.85, result.similarity + 0.15); // Moderate boost for Shell tools only when git terms are explicit
 592 |             logger.debug(`🔧 Git query detected, boosting ${result.toolId} similarity to ${result.similarity}`);
 593 |           }
 594 |         }
 595 |       }
 596 |       
 597 |       // Enhanced filtering with domain awareness
 598 |       const inferredDomains = this.inferQueryDomains(queryLower);
 599 | 
 600 |       // Sort by similarity and apply enhancement system
 601 |       const results = similarities
 602 |         .sort((a, b) => b.similarity - a.similarity)
 603 |         .slice(0, maxResults * 2) // Get more candidates for domain filtering
 604 |         .filter(result => result.similarity > 0.25) // Lower initial threshold for domain filtering
 605 |         .map(result => {
 606 |           const toolData = this.vectorDB.get(result.toolId);
 607 |           let boostedSimilarity = result.similarity;
 608 |           let enhancementReasons: string[] = [];
 609 | 
 610 |           // Apply semantic enhancement engine (capability inference + intent resolution)
 611 |           if (toolData) {
 612 |             const semanticEnhancements = this.semanticEnhancementEngine.applySemanticalEnhancement(
 613 |               query,
 614 |               result.toolId,
 615 |               toolData.description
 616 |             );
 617 | 
 618 |             for (const enhancement of semanticEnhancements) {
 619 |               boostedSimilarity += enhancement.relevanceBoost;
 620 |               enhancementReasons.push(`${enhancement.enhancementType}: ${enhancement.enhancementReason}`);
 621 | 
 622 |               logger.debug(`🚀 Semantic enhancement ${result.toolId}: +${enhancement.relevanceBoost.toFixed(3)} (${enhancement.enhancementType})`);
 623 |             }
 624 |           }
 625 | 
 626 |           // Legacy domain boosting (will be replaced by enhancement system over time)
 627 |           if (toolData?.mcpDomain && inferredDomains.length > 0) {
 628 |             const domainMatch = inferredDomains.some(domain =>
 629 |               toolData.mcpDomain!.toLowerCase().includes(domain.toLowerCase()) ||
 630 |               domain.toLowerCase().includes(toolData.mcpDomain!.toLowerCase())
 631 |             );
 632 |             if (domainMatch) {
 633 |               boostedSimilarity = Math.min(0.98, boostedSimilarity + 0.15);
 634 |               enhancementReasons.push(`legacy: domain match (${toolData.mcpDomain})`);
 635 |             }
 636 |           }
 637 |           
 638 |           const baseReason = toolData?.mcpDomain ?
 639 |             `${toolData.mcpDomain} tool (RAG)` :
 640 |             'Semantic similarity (RAG)';
 641 | 
 642 |           const enhancedReason = enhancementReasons.length > 0 ?
 643 |             `${baseReason} + ${enhancementReasons.join(', ')}` :
 644 |             baseReason;
 645 | 
 646 |           return {
 647 |             toolId: result.toolId,
 648 |             confidence: Math.min(0.95, boostedSimilarity),
 649 |             reason: enhancedReason,
 650 |             similarity: boostedSimilarity,
 651 |             originalSimilarity: result.similarity,
 652 |             domain: toolData?.mcpDomain || 'unknown'
 653 |           };
 654 |         })
 655 |         .sort((a, b) => b.similarity - a.similarity) // Re-sort after boosting
 656 |         .slice(0, maxResults) // Take final top results
 657 |         .filter(result => result.similarity > 0.3); // Final threshold
 658 | 
 659 |       logger.debug(`🎯 Found ${results.length} matches for "${query}"`);
 660 |       
 661 |       return results;
 662 | 
 663 |     } catch (error) {
 664 |       logger.error(`❌ RAG discovery failed: ${error}`);
 665 |       return this.fallbackKeywordSearch(query, maxResults);
 666 |     }
 667 |   }
 668 | 
 669 |   /**
 670 |    * Enhanced fallback keyword search when RAG fails
 671 |    */
 672 |   private fallbackKeywordSearch(query: string, maxResults: number): DiscoveryResult[] {
 673 |     logger.debug('🔄 Using enhanced keyword search');
 674 |     
 675 |     const queryWords = query.toLowerCase().split(/\s+/);
 676 |     const scores = new Map<string, { score: number; matches: string[] }>();
 677 |     
 678 |     // Domain-specific patterns for better disambiguation
 679 |     const domainPatterns: Record<string, { tools: string[]; keywords: string[]; boost: number }> = {
 680 |       'web_search': {
 681 |         tools: ['tavily:search', 'tavily:searchContext', 'tavily:searchQNA'],
 682 |         keywords: ['web', 'internet', 'google', 'online', 'website', 'url', 'tavily', 'search web', 'web search', 'search the web', 'google search', 'search online', 'online search', 'internet search', 'web information', 'search information', 'find online', 'look up online'],
 683 |         boost: 3.0
 684 |       },
 685 |       'code_search': {
 686 |         tools: ['desktop-commander:search_code'],
 687 |         keywords: ['code', 'text', 'pattern', 'grep', 'ripgrep', 'file content', 'search code', 'search text'],
 688 |         boost: 2.0
 689 |       },
 690 |       'file_search': {
 691 |         tools: ['desktop-commander:search_files'],
 692 |         keywords: ['file name', 'filename', 'find file', 'locate file', 'search files'],
 693 |         boost: 2.0
 694 |       },
 695 |       'create_file': {
 696 |         tools: ['desktop-commander:write_file'],
 697 |         keywords: ['create file', 'new file', 'make file', 'generate file'],
 698 |         boost: 3.0
 699 |       },
 700 |       'read_single_file': {
 701 |         tools: ['desktop-commander:read_file'],
 702 |         keywords: ['read file', 'get file', 'show file', 'view file', 'display file', 'file content', 'get content', 'show content', 'view file content', 'display file content', 'read single file', 'show single file'],
 703 |         boost: 5.0
 704 |       },
 705 |       'read_multiple_files': {
 706 |         tools: ['desktop-commander:read_multiple_files'],
 707 |         keywords: ['read multiple files', 'read many files', 'get multiple files', 'show multiple files', 'multiple file content'],
 708 |         boost: 3.0
 709 |       },
 710 |       'git_operations': {
 711 |         tools: ['Shell:run_command', 'desktop-commander:start_process'],
 712 |         keywords: [
 713 |           // Basic git terms
 714 |           'git', 'commit', 'push', 'pull', 'clone', 'branch', 'merge', 'repository',
 715 |           // Full git commands
 716 |           'git commit', 'git push', 'git pull', 'git status', 'git add', 'git log', 'git diff', 'git branch', 'git checkout', 'git merge',
 717 |           // Hyphenated variants (common in user queries)
 718 |           'git-commit', 'git-push', 'git-pull', 'git-status', 'git-add', 'git-log', 'git-diff', 'git-branch', 'git-checkout', 'git-merge',
 719 |           // Action-oriented phrases
 720 |           'commit changes', 'push to git', 'pull from git', 'check git status', 'add files to git', 'create git branch',
 721 |           // Individual commands (for brevity)
 722 |           'checkout', 'add', 'status', 'log', 'diff', 'remote', 'fetch', 'rebase', 'stash', 'tag'
 723 |         ],
 724 |         boost: 8.0
 725 |       },
 726 |       'script_execution': {
 727 |         tools: ['Shell:run_command'],
 728 |         keywords: ['python script', 'bash script', 'shell script', 'run python script', 'execute python script', 'run bash script', 'execute bash script', 'script execution', 'run a python script', 'run a bash script', 'execute a script'],
 729 |         boost: 2.0  // Reduced boost and more specific keywords
 730 |       },
 731 |       'shell_commands': {
 732 |         tools: ['Shell:run_command', 'desktop-commander:start_process'],
 733 |         keywords: ['npm install', 'yarn install', 'pip install', 'terminal command', 'shell command', 'command line interface'],
 734 |         boost: 1.5  // Much lower boost and more specific keywords
 735 |       },
 736 |       'ncp_meta_operations': {
 737 |         tools: [
 738 |           'ncp:list_available_tools',
 739 |           'ncp:check_mcp_health', 
 740 |           'ncp:manage_ncp_profiles',
 741 |           'ncp:show_token_savings',
 742 |           'ncp:get_ncp_status'
 743 |         ],
 744 |         keywords: [
 745 |           // NCP-specific terms (highest priority)
 746 |           'ncp', 'mcp orchestrator', 'ncp orchestrator', 'connected mcps', 'ncp system',
 747 |           
 748 |           // Tool listing (specific to NCP context)
 749 |           'what tools does ncp have', 'ncp available tools', 'mcp tools available', 
 750 |           'tools through ncp', 'ncp functionality', 'what can ncp do', 'available through ncp',
 751 |           'list ncp tools', 'show ncp tools', 'ncp tool list',
 752 |           
 753 |           // Health checking (NCP-specific)
 754 |           'mcp health', 'mcp server health', 'ncp health', 'mcp connection status',
 755 |           'which mcps are working', 'mcp errors', 'server status ncp', 'ncp server status',
 756 |           'check mcp health', 'mcp health check', 'health status ncp',
 757 |           
 758 |           // Profile management (NCP-specific)  
 759 |           'ncp profiles', 'ncp configuration', 'mcp profiles', 'which mcps to load',
 760 |           'ncp setup', 'ncp server configuration', 'execution profiles', 'ncp profile management',
 761 |           'manage ncp profiles', 'ncp profile config', 'profile settings ncp',
 762 |           
 763 |           // Token statistics (NCP-specific)
 764 |           'ncp token savings', 'ncp efficiency', 'how much does ncp save',
 765 |           'ncp performance', 'token usage ncp', 'ncp statistics', 'token savings ncp',
 766 |           'ncp token stats', 'ncp savings report',
 767 |           
 768 |           // System status (NCP-specific)
 769 |           'ncp status', 'ncp info', 'ncp system info', 'what is ncp running',
 770 |           'ncp runtime', 'ncp configuration info', 'ncp system status'
 771 |         ],
 772 |         boost: 8.0  // Very high boost for NCP-specific context
 773 |       }
 774 |     };
 775 |     
 776 |     // Check for domain-specific patterns first
 777 |     const queryLower = query.toLowerCase();
 778 |     
 779 |     // Context detection for disambiguation
 780 |     const hasNcpContext = queryLower.includes('ncp') || 
 781 |                          queryLower.includes('mcp') || 
 782 |                          queryLower.includes('orchestrator') ||
 783 |                          queryLower.includes('connected');
 784 |     
 785 |     // Boost script execution tools but don't force them (let RAG compete)
 786 |     const explicitScriptKeywords = ['python script', 'bash script', 'shell script', 'run python script', 'execute python script', 'run bash script', 'execute bash script'];
 787 |     const hasExplicitScript = explicitScriptKeywords.some(keyword => queryLower.includes(keyword));
 788 | 
 789 |     // Only boost for very explicit script execution queries, not general "run" or "execute"
 790 |     
 791 |     for (const [domain, pattern] of Object.entries(domainPatterns)) {
 792 |       for (const keyword of pattern.keywords) {
 793 |         if (queryLower.includes(keyword)) {
 794 |           for (const toolId of pattern.tools) {
 795 |             if (this.vectorDB.has(toolId)) {
 796 |               const toolData = this.vectorDB.get(toolId)!;
 797 |               const existing = scores.get(toolId) || { score: 0, matches: [] };
 798 |               existing.score += pattern.boost;
 799 |               existing.matches.push(`domain:${domain}:${keyword}`);
 800 |               scores.set(toolId, existing);
 801 |             }
 802 |           }
 803 |         }
 804 |       }
 805 |     }
 806 | 
 807 |     // Apply domain-aware penalties for incidental matches
 808 |     // Tools that mention git but can't actually execute git commands should be deprioritized
 809 |     const incidentalGitPatterns = ['git-style', 'git style', 'git format', 'git diff format'];
 810 |     const actualGitCapabilityTools = ['Shell:run_command', 'desktop-commander:start_process'];
 811 | 
 812 |     if (queryLower.includes('git')) {
 813 |       for (const [toolId, data] of scores) {
 814 |         const toolData = this.vectorDB.get(toolId);
 815 |         if (toolData) {
 816 |           const description = toolData.description.toLowerCase();
 817 |           const hasIncidentalMention = incidentalGitPatterns.some(pattern => description.includes(pattern));
 818 |           const hasActualCapability = actualGitCapabilityTools.includes(toolId) ||
 819 |                                      toolData.enhancedDescription?.includes('Can execute git commands');
 820 | 
 821 |           if (hasIncidentalMention && !hasActualCapability) {
 822 |             // Significantly reduce score for incidental mentions
 823 |             data.score *= 0.3;
 824 |             data.matches.push('penalty:incidental-git-mention');
 825 |           } else if (hasActualCapability) {
 826 |             // Boost tools with actual git capabilities
 827 |             data.score *= 1.5;
 828 |             data.matches.push('boost:actual-git-capability');
 829 |           }
 830 |         }
 831 |       }
 832 |     }
 833 | 
 834 |     // Semantic keyword mappings for general matching
 835 |     const synonyms: Record<string, string[]> = {
 836 |       'create': ['make', 'add', 'new', 'generate', 'build'], // Removed 'write' to avoid confusion
 837 |       'read': ['get', 'fetch', 'load', 'show', 'display', 'view'],
 838 |       'update': ['edit', 'modify', 'change', 'set', 'alter'],
 839 |       'delete': ['remove', 'kill', 'terminate', 'clear', 'destroy'],
 840 |       'file': ['document', 'content', 'text', 'script', 'data'],
 841 |       'list': ['display', 'enumerate'], // Removed 'show' and 'get' to avoid confusion with read operations
 842 |       'search': ['find', 'look', 'query', 'seek'],
 843 |       'run': ['execute', 'start', 'launch', 'invoke'],
 844 |       'process': ['command', 'task', 'service', 'program', 'app']
 845 |     };
 846 |     
 847 |     // Expand query words with synonyms
 848 |     const expandedWords = [...queryWords];
 849 |     for (const word of queryWords) {
 850 |       if (synonyms[word]) {
 851 |         expandedWords.push(...synonyms[word]);
 852 |       }
 853 |     }
 854 |     
 855 |     for (const [toolId, toolData] of this.vectorDB) {
 856 |       const toolName = toolData.toolName.toLowerCase();
 857 |       const description = toolData.description.toLowerCase();
 858 |       const allText = `${toolName} ${description}`;
 859 |       const textWords = allText.split(/\s+/);
 860 |       
 861 |       let score = 0;
 862 |       const matches: string[] = [];
 863 |       
 864 |       // Exact matches get highest score
 865 |       for (const queryWord of queryWords) {
 866 |         if (toolName.includes(queryWord)) {
 867 |           score += 10;
 868 |           matches.push(`name:${queryWord}`);
 869 |         }
 870 |         if (description.includes(queryWord)) {
 871 |           score += 5;
 872 |           matches.push(`desc:${queryWord}`);
 873 |         }
 874 |       }
 875 |       
 876 |       // Synonym matches get medium score
 877 |       for (const expandedWord of expandedWords) {
 878 |         if (expandedWord !== queryWords.find(w => w === expandedWord)) { // Only synonyms
 879 |           if (allText.includes(expandedWord)) {
 880 |             score += 3;
 881 |             matches.push(`syn:${expandedWord}`);
 882 |           }
 883 |         }
 884 |       }
 885 |       
 886 |       // Word containment gets lower score
 887 |       for (const queryWord of queryWords) {
 888 |         for (const textWord of textWords) {
 889 |           if (textWord.includes(queryWord) || queryWord.includes(textWord)) {
 890 |             if (textWord.length > 3 && queryWord.length > 3) {
 891 |               score += 1;
 892 |               matches.push(`partial:${textWord}`);
 893 |             }
 894 |           }
 895 |         }
 896 |       }
 897 |       
 898 |       if (score > 0) {
 899 |         const existing = scores.get(toolId) || { score: 0, matches: [] };
 900 |         existing.score += score; // Add to domain pattern score
 901 |         existing.matches.push(...matches);
 902 |         scores.set(toolId, existing);
 903 |       }
 904 |     }
 905 |     
 906 |     // Apply context-aware scoring adjustments for disambiguation
 907 |     for (const [toolId, data] of scores) {
 908 |       // Reduce NCP tool scores if query lacks NCP/MCP context
 909 |       if (toolId.startsWith('ncp:') && !hasNcpContext) {
 910 |         data.score *= 0.3; // Significant penalty for NCP tools without NCP context
 911 |       }
 912 |       
 913 |       // Boost NCP tool scores if query has NCP/MCP context  
 914 |       if (toolId.startsWith('ncp:') && hasNcpContext) {
 915 |         data.score *= 1.5; // Boost NCP tools when NCP context is present
 916 |       }
 917 |     }
 918 |     
 919 |     return Array.from(scores.entries())
 920 |       .sort((a, b) => {
 921 |         // Prioritize domain pattern matches
 922 |         const aDomainMatches = a[1].matches.filter(m => m.startsWith('domain:')).length;
 923 |         const bDomainMatches = b[1].matches.filter(m => m.startsWith('domain:')).length;
 924 |         
 925 |         if (aDomainMatches !== bDomainMatches) {
 926 |           return bDomainMatches - aDomainMatches; // More domain matches first
 927 |         }
 928 |         
 929 |         // If domain matches are equal, sort by score
 930 |         return b[1].score - a[1].score;
 931 |       })
 932 |       .slice(0, maxResults)
 933 |       .map(([toolId, data]) => {
 934 |         const maxScore = Math.max(...Array.from(scores.values()).map(v => v.score));
 935 |         return {
 936 |           toolId,
 937 |           confidence: Math.min(0.75, data.score / maxScore),
 938 |           reason: `Enhanced keyword matching: ${data.matches.slice(0, 3).join(', ')}`,
 939 |           similarity: data.score / maxScore
 940 |         };
 941 |       });
 942 |   }
 943 | 
 944 |   /**
 945 |    * Calculate cosine similarity between two vectors
 946 |    */
 947 |   private cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number {
 948 |     let dotProduct = 0;
 949 |     let normA = 0;
 950 |     let normB = 0;
 951 |     
 952 |     for (let i = 0; i < a.length; i++) {
 953 |       dotProduct += a[i] * b[i];
 954 |       normA += a[i] * a[i];
 955 |       normB += b[i] * b[i];
 956 |     }
 957 |     
 958 |     return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
 959 |   }
 960 | 
 961 |   /**
 962 |    * Generate hash of tool description for change detection
 963 |    */
 964 |   private hashDescription(description: string): string {
 965 |     return crypto.createHash('md5').update(description).digest('hex');
 966 |   }
 967 | 
 968 |   /**
 969 |    * Load cached embeddings from disk
 970 |    */
 971 |   private async loadPersistedEmbeddings(): Promise<void> {
 972 |     try {
 973 |       if (!existsSync(this.dbPath)) {
 974 |         logger.info('📄 No cached embeddings found, starting fresh');
 975 |         return;
 976 |       }
 977 | 
 978 |       const data = await fs.readFile(this.dbPath, 'utf-8');
 979 |       const cached = JSON.parse(data);
 980 |       
 981 |       for (const [toolId, embedding] of Object.entries(cached)) {
 982 |         const embeddingData = embedding as any;
 983 |         this.vectorDB.set(toolId, {
 984 |           embedding: new Float32Array(embeddingData.embedding),
 985 |           hash: embeddingData.hash,
 986 |           lastUpdated: embeddingData.lastUpdated,
 987 |           toolName: embeddingData.toolName,
 988 |           description: embeddingData.description,
 989 |           enhancedDescription: embeddingData.enhancedDescription,
 990 |           mcpName: embeddingData.mcpName,
 991 |           mcpDomain: embeddingData.mcpDomain
 992 |         });
 993 |       }
 994 | 
 995 |       logger.info(`📥 Loaded ${this.vectorDB.size} cached embeddings`);
 996 |     } catch (error) {
 997 |       logger.warn(`⚠️ Failed to load cached embeddings: ${error}`);
 998 |     }
 999 |   }
1000 | 
1001 |   /**
1002 |    * Persist embeddings to disk
1003 |    */
1004 |   private async persistEmbeddings(): Promise<void> {
1005 |     try {
1006 |       const toSerialize: Record<string, any> = {};
1007 |       
1008 |       for (const [toolId, embedding] of this.vectorDB) {
1009 |         toSerialize[toolId] = {
1010 |           embedding: Array.from(embedding.embedding), // Convert Float32Array to regular array
1011 |           hash: embedding.hash,
1012 |           lastUpdated: embedding.lastUpdated,
1013 |           toolName: embedding.toolName,
1014 |           description: embedding.description
1015 |         };
1016 |       }
1017 | 
1018 |       await fs.writeFile(this.dbPath, JSON.stringify(toSerialize, null, 2));
1019 |       logger.debug(`💾 Persisted ${this.vectorDB.size} embeddings to cache`);
1020 |     } catch (error) {
1021 |       logger.error(`❌ Failed to persist embeddings: ${error}`);
1022 |     }
1023 |   }
1024 | 
1025 |   /**
1026 |    * Ensure directory exists
1027 |    */
1028 |   private ensureDirectoryExists(dirPath: string): void {
1029 |     if (!existsSync(dirPath)) {
1030 |       mkdirSync(dirPath, { recursive: true });
1031 |     }
1032 |   }
1033 | 
1034 |   /**
1035 |    * Get statistics about the RAG engine
1036 |    */
1037 |   getStats(): {
1038 |     isInitialized: boolean;
1039 |     totalEmbeddings: number;
1040 |     queuedTasks: number;
1041 |     isIndexing: boolean;
1042 |     cacheSize: string;
1043 |   } {
1044 |     const stats = {
1045 |       isInitialized: this.isInitialized,
1046 |       totalEmbeddings: this.vectorDB.size,
1047 |       queuedTasks: this.indexingQueue.length,
1048 |       isIndexing: this.isIndexing,
1049 |       cacheSize: '0 KB'
1050 |     };
1051 | 
1052 |     // Calculate cache size
1053 |     try {
1054 |       if (existsSync(this.dbPath)) {
1055 |         const size = statSync(this.dbPath).size;
1056 |         stats.cacheSize = `${Math.round(size / 1024)} KB`;
1057 |       }
1058 |     } catch {
1059 |       // Ignore errors
1060 |     }
1061 | 
1062 |     return stats;
1063 |   }
1064 | 
1065 |   /**
1066 |    * Force cache refresh by clearing and rebuilding
1067 |    */
1068 |   async refreshCache(): Promise<void> {
1069 |     logger.info('🔄 Forcing cache refresh...');
1070 |     await this.clearCache();
1071 |     logger.info('💡 Cache cleared - embeddings will be rebuilt on next indexing');
1072 |   }
1073 | 
1074 |   /**
1075 |    * Clear all cached embeddings and metadata
1076 |    */
1077 |   async clearCache(): Promise<void> {
1078 |     this.vectorDB.clear();
1079 |     this.cacheMetadata = null;
1080 |     
1081 |     try {
1082 |       if (existsSync(this.dbPath)) {
1083 |         await fs.unlink(this.dbPath);
1084 |       }
1085 |       if (existsSync(this.metadataPath)) {
1086 |         await fs.unlink(this.metadataPath);
1087 |       }
1088 |       logger.info('🗑️ Cleared embedding cache and metadata');
1089 |     } catch (error) {
1090 |       logger.error(`❌ Failed to clear cache: ${error}`);
1091 |     }
1092 |   }
1093 | }
```
Page 10/12FirstPrevNextLast