#
tokens: 48746/50000 33/189 files (page 2/12)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 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/transports/filtered-stdio-transport.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Filtered Output for MCP Clients
  3 |  *
  4 |  * Since we're running as a client (not intercepting server output),
  5 |  * we need to filter the console output that leaks through when
  6 |  * executing tools via subprocess MCPs.
  7 |  */
  8 | 
  9 | import { logger } from '../utils/logger.js';
 10 | 
 11 | // Store original console methods
 12 | const originalConsoleLog = console.log;
 13 | const originalConsoleError = console.error;
 14 | const originalConsoleWarn = console.warn;
 15 | const originalConsoleInfo = console.info;
 16 | 
 17 | // Track if filtering is active
 18 | let filteringActive = false;
 19 | 
 20 | /**
 21 |  * List of patterns to filter from console output
 22 |  */
 23 | const FILTER_PATTERNS = [
 24 |   // MCP server startup messages
 25 |   'running on stdio',
 26 |   'MCP Server running',
 27 |   'MCP server running',
 28 |   'Server running on stdio',
 29 |   'Client does not support',
 30 | 
 31 |   // Specific MCP messages
 32 |   'Secure MCP Filesystem Server',
 33 |   'Knowledge Graph MCP Server',
 34 |   'Sequential Thinking MCP Server',
 35 |   'Stripe MCP Server',
 36 | 
 37 |   // Connection messages
 38 |   'Connecting to server:',
 39 |   'Streamable HTTP connection',
 40 |   'Received exit signal',
 41 |   'Starting cleanup process',
 42 |   'Final cleanup on exit',
 43 |   '[Runner]',
 44 | 
 45 |   // Tool execution artifacts
 46 |   'Shell cwd was reset'
 47 | ];
 48 | 
 49 | /**
 50 |  * Check if a message should be filtered
 51 |  */
 52 | function shouldFilter(args: any[]): boolean {
 53 |   if (!filteringActive) return false;
 54 | 
 55 |   const message = args.map(arg =>
 56 |     typeof arg === 'string' ? arg : JSON.stringify(arg)
 57 |   ).join(' ');
 58 | 
 59 |   return FILTER_PATTERNS.some(pattern => message.includes(pattern));
 60 | }
 61 | 
 62 | /**
 63 |  * Create a filtered console method
 64 |  */
 65 | function createFilteredMethod(originalMethod: typeof console.log): typeof console.log {
 66 |   return function(...args: any[]) {
 67 |     if (!shouldFilter(args)) {
 68 |       originalMethod.apply(console, args);
 69 |     } else if (process.env.NCP_DEBUG_FILTER === 'true') {
 70 |       // In debug mode, show what we're filtering
 71 |       originalConsoleError.call(console, '[Filtered]:', ...args);
 72 |     }
 73 |   } as typeof console.log;
 74 | }
 75 | 
 76 | /**
 77 |  * Enable console output filtering
 78 |  */
 79 | export function enableOutputFilter(): void {
 80 |   if (filteringActive) return;
 81 | 
 82 |   filteringActive = true;
 83 |   console.log = createFilteredMethod(originalConsoleLog) as typeof console.log;
 84 |   console.error = createFilteredMethod(originalConsoleError) as typeof console.error;
 85 |   console.warn = createFilteredMethod(originalConsoleWarn) as typeof console.warn;
 86 |   console.info = createFilteredMethod(originalConsoleInfo) as typeof console.info;
 87 | 
 88 |   logger.debug('Console output filtering enabled');
 89 | }
 90 | 
 91 | /**
 92 |  * Disable console output filtering
 93 |  */
 94 | export function disableOutputFilter(): void {
 95 |   if (!filteringActive) return;
 96 | 
 97 |   filteringActive = false;
 98 |   console.log = originalConsoleLog;
 99 |   console.error = originalConsoleError;
100 |   console.warn = originalConsoleWarn;
101 |   console.info = originalConsoleInfo;
102 | 
103 |   logger.debug('Console output filtering disabled');
104 | }
105 | 
106 | /**
107 |  * Execute a function with filtered output
108 |  */
109 | export async function withFilteredOutput<T>(fn: () => Promise<T>): Promise<T> {
110 |   enableOutputFilter();
111 |   try {
112 |     return await fn();
113 |   } finally {
114 |     disableOutputFilter();
115 |   }
116 | }
117 | 
118 | /**
119 |  * Check if we're in CLI mode (where filtering should be applied)
120 |  */
121 | export function shouldApplyFilter(): boolean {
122 |   // Apply filter in CLI mode but not in server mode
123 |   return !process.argv.includes('--server') &&
124 |          !process.env.NCP_MODE?.includes('mcp');
125 | }
```

--------------------------------------------------------------------------------
/test/mock-mcps/stripe-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Stripe MCP Server
  5 |  * Real MCP server structure for payment processing testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'stripe-test',
 12 |   version: '1.0.0',
 13 |   description: 'Complete payment processing for online businesses including charges, subscriptions, and refunds'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'create_payment',
 19 |     description: 'Process credit card payments and charges from customers. Charge customer for order, process payment from customer.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         amount: {
 24 |           type: 'number',
 25 |           description: 'Payment amount in cents'
 26 |         },
 27 |         currency: {
 28 |           type: 'string',
 29 |           description: 'Three-letter currency code (USD, EUR, etc.)'
 30 |         },
 31 |         customer: {
 32 |           type: 'string',
 33 |           description: 'Customer identifier or email'
 34 |         },
 35 |         description: {
 36 |           type: 'string',
 37 |           description: 'Payment description for records'
 38 |         }
 39 |       },
 40 |       required: ['amount', 'currency']
 41 |     }
 42 |   },
 43 |   {
 44 |     name: 'refund_payment',
 45 |     description: 'Process refunds for previously charged payments. Refund cancelled subscription, return customer money.',
 46 |     inputSchema: {
 47 |       type: 'object',
 48 |       properties: {
 49 |         payment_id: {
 50 |           type: 'string',
 51 |           description: 'Original payment identifier to refund'
 52 |         },
 53 |         amount: {
 54 |           type: 'number',
 55 |           description: 'Refund amount in cents (optional, defaults to full amount)'
 56 |         },
 57 |         reason: {
 58 |           type: 'string',
 59 |           description: 'Reason for refund'
 60 |         }
 61 |       },
 62 |       required: ['payment_id']
 63 |     }
 64 |   },
 65 |   {
 66 |     name: 'create_subscription',
 67 |     description: 'Create recurring subscription billing for customers. Set up monthly billing, create subscription plans.',
 68 |     inputSchema: {
 69 |       type: 'object',
 70 |       properties: {
 71 |         customer: {
 72 |           type: 'string',
 73 |           description: 'Customer identifier'
 74 |         },
 75 |         price: {
 76 |           type: 'string',
 77 |           description: 'Subscription price identifier or amount'
 78 |         },
 79 |         trial_days: {
 80 |           type: 'number',
 81 |           description: 'Optional trial period in days'
 82 |         },
 83 |         interval: {
 84 |           type: 'string',
 85 |           description: 'Billing interval (monthly, yearly, etc.)'
 86 |         }
 87 |       },
 88 |       required: ['customer', 'price']
 89 |     }
 90 |   },
 91 |   {
 92 |     name: 'list_payments',
 93 |     description: 'List payment transactions with filtering and pagination. See all payment transactions from today, view payment history.',
 94 |     inputSchema: {
 95 |       type: 'object',
 96 |       properties: {
 97 |         customer: {
 98 |           type: 'string',
 99 |           description: 'Optional customer filter'
100 |         },
101 |         date_range: {
102 |           type: 'object',
103 |           description: 'Optional date range filter with start and end dates'
104 |         },
105 |         status: {
106 |           type: 'string',
107 |           description: 'Optional payment status filter (succeeded, failed, pending)'
108 |         },
109 |         limit: {
110 |           type: 'number',
111 |           description: 'Maximum number of results to return'
112 |         }
113 |       }
114 |     }
115 |   }
116 | ];
117 | 
118 | // Create and run the server
119 | const server = new MockMCPServer(serverInfo, tools);
120 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Feature Request
  2 | description: Suggest a new feature or enhancement for NCP
  3 | title: "[Feature]: "
  4 | labels: ["enhancement", "triage"]
  5 | body:
  6 |   - type: markdown
  7 |     attributes:
  8 |       value: |
  9 |         Thanks for suggesting a new feature! Please provide detailed information to help us understand your request.
 10 | 
 11 |   - type: checkboxes
 12 |     id: terms
 13 |     attributes:
 14 |       label: Prerequisites
 15 |       description: Please confirm the following before submitting
 16 |       options:
 17 |         - label: I have searched existing issues to ensure this feature hasn't been requested
 18 |           required: true
 19 |         - label: I have checked the roadmap to see if this feature is already planned
 20 |           required: true
 21 | 
 22 |   - type: dropdown
 23 |     id: type
 24 |     attributes:
 25 |       label: Feature Type
 26 |       description: What type of feature is this?
 27 |       options:
 28 |         - New MCP Server Support
 29 |         - CLI Enhancement
 30 |         - Discovery/Search Improvement
 31 |         - Performance Optimization
 32 |         - Developer Experience
 33 |         - Documentation
 34 |         - Other
 35 |     validations:
 36 |       required: true
 37 | 
 38 |   - type: textarea
 39 |     id: problem
 40 |     attributes:
 41 |       label: Problem Statement
 42 |       description: What problem does this feature solve?
 43 |       placeholder: "As a [user type], I want [functionality] so that [benefit]"
 44 |     validations:
 45 |       required: true
 46 | 
 47 |   - type: textarea
 48 |     id: solution
 49 |     attributes:
 50 |       label: Proposed Solution
 51 |       description: Describe your ideal solution for this problem
 52 |       placeholder: What would you like to see implemented?
 53 |     validations:
 54 |       required: true
 55 | 
 56 |   - type: textarea
 57 |     id: alternatives
 58 |     attributes:
 59 |       label: Alternatives Considered
 60 |       description: What other approaches have you considered?
 61 |       placeholder: Are there workarounds or alternative solutions you've tried?
 62 | 
 63 |   - type: dropdown
 64 |     id: priority
 65 |     attributes:
 66 |       label: Priority Level
 67 |       description: How important is this feature to you?
 68 |       options:
 69 |         - Critical - Blocking my workflow
 70 |         - High - Would significantly improve my workflow
 71 |         - Medium - Nice to have improvement
 72 |         - Low - Minor convenience
 73 |     validations:
 74 |       required: true
 75 | 
 76 |   - type: textarea
 77 |     id: use-cases
 78 |     attributes:
 79 |       label: Use Cases
 80 |       description: Describe specific scenarios where this feature would be useful
 81 |       placeholder: |
 82 |         1. When working with [specific MCP/workflow]...
 83 |         2. During [specific development phase]...
 84 |         3. For users who need to [specific task]...
 85 | 
 86 |   - type: textarea
 87 |     id: examples
 88 |     attributes:
 89 |       label: Examples/Mockups
 90 |       description: |
 91 |         Provide examples, mockups, or references to similar implementations
 92 |         You can attach images or link to examples from other tools
 93 | 
 94 |   - type: checkboxes
 95 |     id: contribution
 96 |     attributes:
 97 |       label: Contribution
 98 |       description: Would you be interested in contributing to this feature?
 99 |       options:
100 |         - label: I would be willing to help implement this feature
101 |         - label: I can provide testing/feedback during development
102 |         - label: I can help with documentation
103 | 
104 |   - type: textarea
105 |     id: additional
106 |     attributes:
107 |       label: Additional Context
108 |       description: Any other information that would help us understand this request
109 |       placeholder: Links to relevant documentation, similar features in other tools, etc.
```

--------------------------------------------------------------------------------
/test/mock-mcps/slack-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Slack MCP Server
  5 |  * Real MCP server structure for Slack integration testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'slack-test',
 12 |   version: '1.0.0',
 13 |   description: 'Slack integration for messaging, channel management, file sharing, and team communication'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'send_message',
 19 |     description: 'Send messages to Slack channels or direct messages. Share updates, notify teams, communicate with colleagues.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         channel: {
 24 |           type: 'string',
 25 |           description: 'Channel name or user ID to send message to'
 26 |         },
 27 |         text: {
 28 |           type: 'string',
 29 |           description: 'Message content to send'
 30 |         },
 31 |         thread_ts: {
 32 |           type: 'string',
 33 |           description: 'Optional thread timestamp for replies'
 34 |         }
 35 |       },
 36 |       required: ['channel', 'text']
 37 |     }
 38 |   },
 39 |   {
 40 |     name: 'create_channel',
 41 |     description: 'Create new Slack channels for team collaboration. Set up project channels, organize team discussions.',
 42 |     inputSchema: {
 43 |       type: 'object',
 44 |       properties: {
 45 |         name: {
 46 |           type: 'string',
 47 |           description: 'Channel name'
 48 |         },
 49 |         purpose: {
 50 |           type: 'string',
 51 |           description: 'Channel purpose description'
 52 |         },
 53 |         private: {
 54 |           type: 'boolean',
 55 |           description: 'Whether channel should be private'
 56 |         }
 57 |       },
 58 |       required: ['name']
 59 |     }
 60 |   },
 61 |   {
 62 |     name: 'upload_file',
 63 |     description: 'Upload files to Slack channels for sharing and collaboration. Share documents, images, code files.',
 64 |     inputSchema: {
 65 |       type: 'object',
 66 |       properties: {
 67 |         file: {
 68 |           type: 'string',
 69 |           description: 'File path or content to upload'
 70 |         },
 71 |         channels: {
 72 |           type: 'string',
 73 |           description: 'Comma-separated list of channel names'
 74 |         },
 75 |         title: {
 76 |           type: 'string',
 77 |           description: 'File title'
 78 |         },
 79 |         initial_comment: {
 80 |           type: 'string',
 81 |           description: 'Initial comment when sharing file'
 82 |         }
 83 |       },
 84 |       required: ['file', 'channels']
 85 |     }
 86 |   },
 87 |   {
 88 |     name: 'get_channel_history',
 89 |     description: 'Retrieve message history from Slack channels. Read past conversations, search team discussions.',
 90 |     inputSchema: {
 91 |       type: 'object',
 92 |       properties: {
 93 |         channel: {
 94 |           type: 'string',
 95 |           description: 'Channel ID to get history from'
 96 |         },
 97 |         count: {
 98 |           type: 'number',
 99 |           description: 'Number of messages to retrieve'
100 |         },
101 |         oldest: {
102 |           type: 'string',
103 |           description: 'Oldest timestamp for message range'
104 |         },
105 |         latest: {
106 |           type: 'string',
107 |           description: 'Latest timestamp for message range'
108 |         }
109 |       },
110 |       required: ['channel']
111 |     }
112 |   },
113 |   {
114 |     name: 'set_channel_topic',
115 |     description: 'Set or update channel topic and purpose. Update channel information, set discussion guidelines.',
116 |     inputSchema: {
117 |       type: 'object',
118 |       properties: {
119 |         channel: {
120 |           type: 'string',
121 |           description: 'Channel ID'
122 |         },
123 |         topic: {
124 |           type: 'string',
125 |           description: 'New channel topic'
126 |         }
127 |       },
128 |       required: ['channel', 'topic']
129 |     }
130 |   }
131 | ];
132 | 
133 | // Create and run the server
134 | const server = new MockMCPServer(serverInfo, tools);
135 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "manifest_version": "0.2",
 3 |   "name": "ncp",
 4 |   "display_name": "NCP - Natural Context Provider",
 5 |   "version": "1.5.0",
 6 |   "description": "N-to-1 MCP Orchestration. Unified gateway for multiple MCP servers with intelligent tool discovery and auto-import.",
 7 |   "long_description": "NCP transforms N scattered MCP servers into 1 intelligent orchestrator. Your AI sees just 2 simple tools instead of 50+ complex ones, while NCP handles all the routing, discovery, and execution behind the scenes. Features: semantic search, token optimization (97% reduction), automatic tool discovery, multi-client auto-import (Claude Desktop, Perplexity, Cursor, Cline, Continue), dynamic runtime detection, and optional global CLI access.",
 8 |   "author": {
 9 |     "name": "Portel",
10 |     "url": "https://github.com/portel-dev/ncp"
11 |   },
12 |   "user_config": {
13 |     "profile": {
14 |       "type": "string",
15 |       "title": "Profile Name",
16 |       "description": "Which NCP profile to use (e.g., 'all', 'development', 'minimal'). Auto-imported MCPs from your MCP client will be added to this profile.",
17 |       "default": "all"
18 |     },
19 |     "config_path": {
20 |       "type": "string",
21 |       "title": "Configuration Path",
22 |       "description": "Where to store NCP configurations. Use '~/.ncp' for global (shared across projects), '.ncp' for local (per-project), or specify custom path.",
23 |       "default": "~/.ncp"
24 |     },
25 |     "enable_global_cli": {
26 |       "type": "boolean",
27 |       "title": "Enable Global CLI Access",
28 |       "description": "Create a global 'ncp' command for terminal usage. Allows running NCP from command line anywhere on your system.",
29 |       "default": false
30 |     },
31 |     "auto_import_client_mcps": {
32 |       "type": "boolean",
33 |       "title": "Auto-import Client MCPs",
34 |       "description": "Automatically import all MCPs from your MCP client (Claude Desktop, Perplexity, Cursor, etc.) on startup. Syncs both config files and extensions.",
35 |       "default": true
36 |     },
37 |     "enable_debug_logging": {
38 |       "type": "boolean",
39 |       "title": "Enable Debug Logging",
40 |       "description": "Show detailed logs for troubleshooting (runtime detection, MCP loading, etc.)",
41 |       "default": false
42 |     }
43 |   },
44 |   "server": {
45 |     "type": "node",
46 |     "entry_point": "dist/index-mcp.js",
47 |     "mcp_config": {
48 |       "command": "node",
49 |       "args": [
50 |         "${__dirname}/dist/index-mcp.js",
51 |         "--profile=${user_config.profile}",
52 |         "--config-path=${user_config.config_path}"
53 |       ],
54 |       "env": {
55 |         "NCP_PROFILE": "${user_config.profile}",
56 |         "NCP_CONFIG_PATH": "${user_config.config_path}",
57 |         "NCP_ENABLE_GLOBAL_CLI": "${user_config.enable_global_cli}",
58 |         "NCP_AUTO_IMPORT": "${user_config.auto_import_client_mcps}",
59 |         "NCP_DEBUG": "${user_config.enable_debug_logging}",
60 |         "NCP_MODE": "extension"
61 |       }
62 |     }
63 |   },
64 |   "tools": [
65 |     {
66 |       "name": "find",
67 |       "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. (2) LISTING MODE: Call without description parameter for paginated browsing of all available MCPs and tools."
68 |     },
69 |     {
70 |       "name": "run",
71 |       "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."
72 |     }
73 |   ],
74 |   "keywords": ["mcp", "model-context-protocol", "ai-orchestration", "tool-discovery", "multi-mcp", "claude", "ai-gateway", "auto-import"],
75 |   "license": "Elastic-2.0"
76 | }
77 | 
```

--------------------------------------------------------------------------------
/test/mock-mcps/github-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock GitHub MCP Server
  5 |  * Real MCP server structure for GitHub API integration testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'github-test',
 12 |   version: '1.0.0',
 13 |   description: 'GitHub API integration for repository management, file operations, issues, and pull requests'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'create_repository',
 19 |     description: 'Create a new GitHub repository with configuration options. Set up new project, initialize repository.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         name: {
 24 |           type: 'string',
 25 |           description: 'Repository name'
 26 |         },
 27 |         description: {
 28 |           type: 'string',
 29 |           description: 'Repository description'
 30 |         },
 31 |         private: {
 32 |           type: 'boolean',
 33 |           description: 'Whether repository should be private'
 34 |         },
 35 |         auto_init: {
 36 |           type: 'boolean',
 37 |           description: 'Initialize with README'
 38 |         }
 39 |       },
 40 |       required: ['name']
 41 |     }
 42 |   },
 43 |   {
 44 |     name: 'create_issue',
 45 |     description: 'Create GitHub issues for bug reports and feature requests. Report bugs, request features, track tasks.',
 46 |     inputSchema: {
 47 |       type: 'object',
 48 |       properties: {
 49 |         title: {
 50 |           type: 'string',
 51 |           description: 'Issue title'
 52 |         },
 53 |         body: {
 54 |           type: 'string',
 55 |           description: 'Issue description'
 56 |         },
 57 |         labels: {
 58 |           type: 'array',
 59 |           description: 'Issue labels',
 60 |           items: { type: 'string' }
 61 |         },
 62 |         assignees: {
 63 |           type: 'array',
 64 |           description: 'User assignments',
 65 |           items: { type: 'string' }
 66 |         }
 67 |       },
 68 |       required: ['title']
 69 |     }
 70 |   },
 71 |   {
 72 |     name: 'create_pull_request',
 73 |     description: 'Create pull requests for code review and merging changes. Submit code changes, request reviews.',
 74 |     inputSchema: {
 75 |       type: 'object',
 76 |       properties: {
 77 |         title: {
 78 |           type: 'string',
 79 |           description: 'Pull request title'
 80 |         },
 81 |         body: {
 82 |           type: 'string',
 83 |           description: 'Pull request description'
 84 |         },
 85 |         head: {
 86 |           type: 'string',
 87 |           description: 'Source branch'
 88 |         },
 89 |         base: {
 90 |           type: 'string',
 91 |           description: 'Target branch'
 92 |         }
 93 |       },
 94 |       required: ['title', 'head', 'base']
 95 |     }
 96 |   },
 97 |   {
 98 |     name: 'get_file_contents',
 99 |     description: 'Read file contents from GitHub repositories. Access source code, read configuration files.',
100 |     inputSchema: {
101 |       type: 'object',
102 |       properties: {
103 |         path: {
104 |           type: 'string',
105 |           description: 'File path in repository'
106 |         },
107 |         ref: {
108 |           type: 'string',
109 |           description: 'Branch or commit reference'
110 |         }
111 |       },
112 |       required: ['path']
113 |     }
114 |   },
115 |   {
116 |     name: 'search_repositories',
117 |     description: 'Search GitHub repositories by keywords, topics, and filters. Find open source projects, discover libraries.',
118 |     inputSchema: {
119 |       type: 'object',
120 |       properties: {
121 |         query: {
122 |           type: 'string',
123 |           description: 'Search query with keywords'
124 |         },
125 |         sort: {
126 |           type: 'string',
127 |           description: 'Sort criteria (stars, forks, updated)'
128 |         },
129 |         order: {
130 |           type: 'string',
131 |           description: 'Sort order (asc, desc)'
132 |         }
133 |       },
134 |       required: ['query']
135 |     }
136 |   }
137 | ];
138 | 
139 | // Create and run the server
140 | const server = new MockMCPServer(serverInfo, tools);
141 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/test/mock-mcps/notion-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Notion MCP Server
  5 |  * Real MCP server structure for Notion workspace integration testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'notion-test',
 12 |   version: '1.0.0',
 13 |   description: 'Notion workspace management for documents, databases, and collaborative content creation'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'create_page',
 19 |     description: 'Create new Notion pages and documents with content. Write notes, create documentation, start new projects.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         parent: {
 24 |           type: 'string',
 25 |           description: 'Parent page or database ID'
 26 |         },
 27 |         title: {
 28 |           type: 'string',
 29 |           description: 'Page title'
 30 |         },
 31 |         content: {
 32 |           type: 'array',
 33 |           description: 'Page content blocks'
 34 |         },
 35 |         properties: {
 36 |           type: 'object',
 37 |           description: 'Page properties if parent is database'
 38 |         }
 39 |       },
 40 |       required: ['parent', 'title']
 41 |     }
 42 |   },
 43 |   {
 44 |     name: 'create_database',
 45 |     description: 'Create structured Notion databases with properties and schema. Set up project tracking, create data tables.',
 46 |     inputSchema: {
 47 |       type: 'object',
 48 |       properties: {
 49 |         parent: {
 50 |           type: 'string',
 51 |           description: 'Parent page ID'
 52 |         },
 53 |         title: {
 54 |           type: 'string',
 55 |           description: 'Database title'
 56 |         },
 57 |         properties: {
 58 |           type: 'object',
 59 |           description: 'Database schema properties'
 60 |         },
 61 |         description: {
 62 |           type: 'string',
 63 |           description: 'Database description'
 64 |         }
 65 |       },
 66 |       required: ['parent', 'title', 'properties']
 67 |     }
 68 |   },
 69 |   {
 70 |     name: 'query_database',
 71 |     description: 'Query Notion databases with filtering and sorting. Search data, find records, analyze information.',
 72 |     inputSchema: {
 73 |       type: 'object',
 74 |       properties: {
 75 |         database_id: {
 76 |           type: 'string',
 77 |           description: 'Database ID to query'
 78 |         },
 79 |         filter: {
 80 |           type: 'object',
 81 |           description: 'Query filter conditions'
 82 |         },
 83 |         sorts: {
 84 |           type: 'array',
 85 |           description: 'Sort criteria'
 86 |         },
 87 |         start_cursor: {
 88 |           type: 'string',
 89 |           description: 'Pagination cursor'
 90 |         }
 91 |       },
 92 |       required: ['database_id']
 93 |     }
 94 |   },
 95 |   {
 96 |     name: 'update_page',
 97 |     description: 'Update existing Notion pages with new content and properties. Edit documents, modify data, update information.',
 98 |     inputSchema: {
 99 |       type: 'object',
100 |       properties: {
101 |         page_id: {
102 |           type: 'string',
103 |           description: 'Page ID to update'
104 |         },
105 |         properties: {
106 |           type: 'object',
107 |           description: 'Properties to update'
108 |         },
109 |         content: {
110 |           type: 'array',
111 |           description: 'New content blocks to append'
112 |         }
113 |       },
114 |       required: ['page_id']
115 |     }
116 |   },
117 |   {
118 |     name: 'search_pages',
119 |     description: 'Search across Notion workspace for pages and content. Find documents, locate information, discover content.',
120 |     inputSchema: {
121 |       type: 'object',
122 |       properties: {
123 |         query: {
124 |           type: 'string',
125 |           description: 'Search query text'
126 |         },
127 |         filter: {
128 |           type: 'object',
129 |           description: 'Search filter criteria'
130 |         },
131 |         sort: {
132 |           type: 'object',
133 |           description: 'Sort results by criteria'
134 |         }
135 |       }
136 |     }
137 |   }
138 | ];
139 | 
140 | // Create and run the server
141 | const server = new MockMCPServer(serverInfo, tools);
142 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/docs/pr-schema-additions.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Configuration Schema Types
  3 |  *
  4 |  * These types should be added to schema/draft/schema.ts
  5 |  */
  6 | 
  7 | /**
  8 |  * Describes a configuration parameter needed by the server.
  9 |  */
 10 | export interface ConfigurationParameter {
 11 |   /**
 12 |    * Unique identifier for this parameter (e.g., "GITHUB_TOKEN", "allowed-directory")
 13 |    */
 14 |   name: string;
 15 | 
 16 |   /**
 17 |    * Human-readable description of what this parameter is for
 18 |    */
 19 |   description: string;
 20 | 
 21 |   /**
 22 |    * Type of the parameter value
 23 |    */
 24 |   type: "string" | "number" | "boolean" | "path" | "url";
 25 | 
 26 |   /**
 27 |    * Whether this parameter is required for the server to function
 28 |    */
 29 |   required: boolean;
 30 | 
 31 |   /**
 32 |    * Whether this contains sensitive data (passwords, API keys)
 33 |    * If true, clients should mask input when prompting users
 34 |    */
 35 |   sensitive?: boolean;
 36 | 
 37 |   /**
 38 |    * Default value if not provided by the user
 39 |    */
 40 |   default?: string | number | boolean;
 41 | 
 42 |   /**
 43 |    * Whether multiple values are allowed (for array parameters)
 44 |    */
 45 |   multiple?: boolean;
 46 | 
 47 |   /**
 48 |    * Validation pattern (regex) for string parameters
 49 |    */
 50 |   pattern?: string;
 51 | 
 52 |   /**
 53 |    * Example values to help users understand expected format
 54 |    */
 55 |   examples?: string[];
 56 | }
 57 | 
 58 | /**
 59 |  * Declares configuration requirements for the server.
 60 |  *
 61 |  * Servers can use this to communicate what environment variables,
 62 |  * command-line arguments, or other configuration they need to function properly.
 63 |  *
 64 |  * This enables clients to:
 65 |  * - Detect missing configuration before attempting connection
 66 |  * - Prompt users interactively for required values
 67 |  * - Validate configuration before startup
 68 |  * - Provide helpful error messages
 69 |  */
 70 | export interface ConfigurationSchema {
 71 |   /**
 72 |    * Environment variables required by the server
 73 |    */
 74 |   environmentVariables?: ConfigurationParameter[];
 75 | 
 76 |   /**
 77 |    * Command-line arguments required by the server
 78 |    */
 79 |   arguments?: ConfigurationParameter[];
 80 | 
 81 |   /**
 82 |    * Other configuration requirements (files, URLs, etc.)
 83 |    */
 84 |   other?: ConfigurationParameter[];
 85 | }
 86 | 
 87 | /**
 88 |  * MODIFICATION TO EXISTING InitializeResult INTERFACE
 89 |  *
 90 |  * Add this field to the existing InitializeResult interface:
 91 |  */
 92 | export interface InitializeResult extends Result {
 93 |   protocolVersion: string;
 94 |   capabilities: ServerCapabilities;
 95 |   serverInfo: Implementation;
 96 |   instructions?: string;
 97 | 
 98 |   /**
 99 |    * Optional schema declaring the server's configuration requirements.
100 |    *
101 |    * Servers can use this to communicate what environment variables,
102 |    * command-line arguments, or other configuration they need.
103 |    *
104 |    * Clients can use this information to:
105 |    * - Validate configuration before attempting connection
106 |    * - Prompt users for missing required configuration
107 |    * - Provide better error messages and setup guidance
108 |    *
109 |    * This field is optional and backward compatible - servers that don't
110 |    * provide it continue to work as before.
111 |    *
112 |    * @example
113 |    * ```typescript
114 |    * // Filesystem server declaring path requirement
115 |    * {
116 |    *   "configurationSchema": {
117 |    *     "arguments": [{
118 |    *       "name": "allowed-directory",
119 |    *       "description": "Directory path that the server is allowed to access",
120 |    *       "type": "path",
121 |    *       "required": true,
122 |    *       "multiple": true
123 |    *     }]
124 |    *   }
125 |    * }
126 |    *
127 |    * // API server declaring token requirement
128 |    * {
129 |    *   "configurationSchema": {
130 |    *     "environmentVariables": [{
131 |    *       "name": "GITHUB_TOKEN",
132 |    *       "description": "GitHub personal access token with repo permissions",
133 |    *       "type": "string",
134 |    *       "required": true,
135 |    *       "sensitive": true,
136 |    *       "pattern": "^ghp_[a-zA-Z0-9]{36}$"
137 |    *     }]
138 |    *   }
139 |    * }
140 |    * ```
141 |    */
142 |   configurationSchema?: ConfigurationSchema;
143 | }
144 | 
```

--------------------------------------------------------------------------------
/test/mock-mcps/neo4j-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Neo4j MCP Server
  5 |  * Real MCP server structure for Neo4j graph database testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'neo4j-test',
 12 |   version: '1.0.0',
 13 |   description: 'Neo4j graph database server with schema management and read/write cypher operations'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'execute_cypher',
 19 |     description: 'Execute Cypher queries on Neo4j graph database. Query relationships, find patterns, analyze connections.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         query: {
 24 |           type: 'string',
 25 |           description: 'Cypher query string'
 26 |         },
 27 |         parameters: {
 28 |           type: 'object',
 29 |           description: 'Query parameters as key-value pairs'
 30 |         },
 31 |         database: {
 32 |           type: 'string',
 33 |           description: 'Target database name'
 34 |         }
 35 |       },
 36 |       required: ['query']
 37 |     }
 38 |   },
 39 |   {
 40 |     name: 'create_node',
 41 |     description: 'Create nodes in Neo4j graph with labels and properties. Add entities, create graph elements.',
 42 |     inputSchema: {
 43 |       type: 'object',
 44 |       properties: {
 45 |         labels: {
 46 |           type: 'array',
 47 |           description: 'Node labels',
 48 |           items: { type: 'string' }
 49 |         },
 50 |         properties: {
 51 |           type: 'object',
 52 |           description: 'Node properties as key-value pairs'
 53 |         }
 54 |       },
 55 |       required: ['labels']
 56 |     }
 57 |   },
 58 |   {
 59 |     name: 'create_relationship',
 60 |     description: 'Create relationships between nodes in Neo4j graph. Connect entities, define associations, build graph structure.',
 61 |     inputSchema: {
 62 |       type: 'object',
 63 |       properties: {
 64 |         from_node_id: {
 65 |           type: 'string',
 66 |           description: 'Source node ID'
 67 |         },
 68 |         to_node_id: {
 69 |           type: 'string',
 70 |           description: 'Target node ID'
 71 |         },
 72 |         relationship_type: {
 73 |           type: 'string',
 74 |           description: 'Relationship type/label'
 75 |         },
 76 |         properties: {
 77 |           type: 'object',
 78 |           description: 'Relationship properties'
 79 |         }
 80 |       },
 81 |       required: ['from_node_id', 'to_node_id', 'relationship_type']
 82 |     }
 83 |   },
 84 |   {
 85 |     name: 'find_path',
 86 |     description: 'Find paths between nodes in Neo4j graph database. Discover connections, analyze relationships, trace routes.',
 87 |     inputSchema: {
 88 |       type: 'object',
 89 |       properties: {
 90 |         start_node: {
 91 |           type: 'object',
 92 |           description: 'Starting node criteria'
 93 |         },
 94 |         end_node: {
 95 |           type: 'object',
 96 |           description: 'Ending node criteria'
 97 |         },
 98 |         relationship_types: {
 99 |           type: 'array',
100 |           description: 'Allowed relationship types',
101 |           items: { type: 'string' }
102 |         },
103 |         max_depth: {
104 |           type: 'number',
105 |           description: 'Maximum path depth'
106 |         }
107 |       },
108 |       required: ['start_node', 'end_node']
109 |     }
110 |   },
111 |   {
112 |     name: 'manage_schema',
113 |     description: 'Manage Neo4j database schema including indexes and constraints. Optimize queries, ensure data integrity.',
114 |     inputSchema: {
115 |       type: 'object',
116 |       properties: {
117 |         action: {
118 |           type: 'string',
119 |           description: 'Schema action (create_index, drop_index, create_constraint, drop_constraint)'
120 |         },
121 |         label: {
122 |           type: 'string',
123 |           description: 'Node label or relationship type'
124 |         },
125 |         properties: {
126 |           type: 'array',
127 |           description: 'Properties for index/constraint',
128 |           items: { type: 'string' }
129 |         },
130 |         constraint_type: {
131 |           type: 'string',
132 |           description: 'Constraint type (unique, exists, key)'
133 |         }
134 |       },
135 |       required: ['action', 'label', 'properties']
136 |     }
137 |   }
138 | ];
139 | 
140 | // Create and run the server
141 | const server = new MockMCPServer(serverInfo, tools);
142 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/src/testing/setup-dummy-mcps.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Setup Dummy MCPs for Testing
  4 |  *
  5 |  * Creates NCP profile configurations that use dummy MCP servers for testing
  6 |  * the semantic enhancement system without requiring real MCP connections.
  7 |  */
  8 | 
  9 | import * as fs from 'fs/promises';
 10 | import * as path from 'path';
 11 | import { fileURLToPath } from 'url';
 12 | import { getNcpBaseDirectory } from '../utils/ncp-paths.js';
 13 | 
 14 | const __filename = fileURLToPath(import.meta.url);
 15 | const __dirname = path.dirname(__filename);
 16 | 
 17 | interface McpDefinitionsFile {
 18 |   mcps: Record<string, any>;
 19 | }
 20 | 
 21 | async function setupDummyMcps(): Promise<void> {
 22 |   try {
 23 |     // Load MCP definitions
 24 |     const definitionsPath = path.join(__dirname, 'mcp-definitions.json');
 25 |     const definitionsContent = await fs.readFile(definitionsPath, 'utf-8');
 26 |     const definitions: McpDefinitionsFile = JSON.parse(definitionsContent);
 27 | 
 28 |     // Get NCP base directory and ensure profiles directory exists
 29 |     const ncpBaseDir = await getNcpBaseDirectory();
 30 |     const profilesDir = path.join(ncpBaseDir, 'profiles');
 31 |     await fs.mkdir(profilesDir, { recursive: true });
 32 | 
 33 |     // Create test profile configuration
 34 |     const profileConfig = {
 35 |       name: "semantic-test",
 36 |       description: "Testing profile with dummy MCPs for semantic enhancement validation",
 37 |       mcpServers: {} as Record<string, any>,
 38 |       metadata: {
 39 |         created: new Date().toISOString(),
 40 |         modified: new Date().toISOString()
 41 |       }
 42 |     };
 43 | 
 44 |     // Build dummy MCP server path
 45 |     const dummyServerPath = path.join(__dirname, 'dummy-mcp-server.ts');
 46 |     const nodeExecutable = process.execPath;
 47 |     const tsNodePath = path.join(path.dirname(nodeExecutable), 'npx');
 48 | 
 49 |     // Add each MCP from definitions as a dummy MCP
 50 |     for (const [mcpName, mcpDef] of Object.entries(definitions.mcps)) {
 51 |       profileConfig.mcpServers[mcpName] = {
 52 |         command: 'npx',
 53 |         args: [
 54 |           'tsx', // Use tsx to run TypeScript directly
 55 |           dummyServerPath,
 56 |           '--mcp-name',
 57 |           mcpName,
 58 |           '--definitions-file',
 59 |           definitionsPath
 60 |         ]
 61 |       };
 62 |     }
 63 | 
 64 |     // Write profile configuration
 65 |     const profilePath = path.join(profilesDir, 'semantic-test.json');
 66 |     await fs.writeFile(profilePath, JSON.stringify(profileConfig, null, 2));
 67 | 
 68 |     console.log(`✅ Created semantic-test profile with ${Object.keys(definitions.mcps).length} dummy MCPs:`);
 69 |     Object.keys(definitions.mcps).forEach(name => {
 70 |       console.log(`   - ${name}: ${definitions.mcps[name].description}`);
 71 |     });
 72 |     console.log(`\nProfile saved to: ${profilePath}`);
 73 |     console.log(`\nTo use this profile:`);
 74 |     console.log(`  npx ncp --profile semantic-test list`);
 75 |     console.log(`  npx ncp --profile semantic-test find "commit my code to git"`);
 76 |     console.log(`  npx ncp --profile semantic-test run git:commit --params '{"message":"test commit"}'`);
 77 | 
 78 |     // Create a simplified test profile with just key MCPs for faster testing
 79 |     const quickTestConfig = {
 80 |       name: "semantic-quick",
 81 |       description: "Quick test profile with essential MCPs for semantic enhancement",
 82 |       mcpServers: {
 83 |         shell: profileConfig.mcpServers.shell,
 84 |         git: profileConfig.mcpServers.git,
 85 |         postgres: profileConfig.mcpServers.postgres,
 86 |         openai: profileConfig.mcpServers.openai
 87 |       },
 88 |       metadata: {
 89 |         created: new Date().toISOString(),
 90 |         modified: new Date().toISOString()
 91 |       }
 92 |     };
 93 | 
 94 |     const quickProfilePath = path.join(profilesDir, 'semantic-quick.json');
 95 |     await fs.writeFile(quickProfilePath, JSON.stringify(quickTestConfig, null, 2));
 96 | 
 97 |     console.log(`\n✅ Created semantic-quick profile with 4 essential MCPs`);
 98 |     console.log(`Profile saved to: ${quickProfilePath}`);
 99 | 
100 |   } catch (error) {
101 |     console.error('Failed to setup dummy MCPs:', error);
102 |     process.exit(1);
103 |   }
104 | }
105 | 
106 | // Main execution
107 | if (import.meta.url === `file://${process.argv[1]}`) {
108 |   setupDummyMcps();
109 | }
```

--------------------------------------------------------------------------------
/src/services/config-schema-reader.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Configuration Schema Reader
  3 |  *
  4 |  * Reads configurationSchema from MCP InitializeResult
  5 |  * Caches schemas for future use during `ncp add` and `ncp repair`
  6 |  */
  7 | 
  8 | export interface ConfigurationParameter {
  9 |   name: string;
 10 |   description: string;
 11 |   type: 'string' | 'number' | 'boolean' | 'path' | 'url';
 12 |   required: boolean;
 13 |   sensitive?: boolean;
 14 |   default?: string | number | boolean;
 15 |   multiple?: boolean;
 16 |   pattern?: string;
 17 |   examples?: string[];
 18 | }
 19 | 
 20 | export interface ConfigurationSchema {
 21 |   environmentVariables?: ConfigurationParameter[];
 22 |   arguments?: ConfigurationParameter[];
 23 |   other?: ConfigurationParameter[];
 24 | }
 25 | 
 26 | export interface InitializeResult {
 27 |   protocolVersion: string;
 28 |   capabilities: any;
 29 |   serverInfo: {
 30 |     name: string;
 31 |     version: string;
 32 |   };
 33 |   instructions?: string;
 34 |   configurationSchema?: ConfigurationSchema;
 35 | }
 36 | 
 37 | export class ConfigSchemaReader {
 38 |   /**
 39 |    * Extract configuration schema from InitializeResult
 40 |    */
 41 |   readSchema(initResult: InitializeResult): ConfigurationSchema | null {
 42 |     if (!initResult || !initResult.configurationSchema) {
 43 |       return null;
 44 |     }
 45 | 
 46 |     return initResult.configurationSchema;
 47 |   }
 48 | 
 49 |   /**
 50 |    * Get all required parameters from schema
 51 |    */
 52 |   getRequiredParameters(schema: ConfigurationSchema): ConfigurationParameter[] {
 53 |     const required: ConfigurationParameter[] = [];
 54 | 
 55 |     if (schema.environmentVariables) {
 56 |       required.push(...schema.environmentVariables.filter(p => p.required));
 57 |     }
 58 | 
 59 |     if (schema.arguments) {
 60 |       required.push(...schema.arguments.filter(p => p.required));
 61 |     }
 62 | 
 63 |     if (schema.other) {
 64 |       required.push(...schema.other.filter(p => p.required));
 65 |     }
 66 | 
 67 |     return required;
 68 |   }
 69 | 
 70 |   /**
 71 |    * Check if schema has any required parameters
 72 |    */
 73 |   hasRequiredConfig(schema: ConfigurationSchema | null): boolean {
 74 |     if (!schema) return false;
 75 | 
 76 |     return this.getRequiredParameters(schema).length > 0;
 77 |   }
 78 | 
 79 |   /**
 80 |    * Get parameter by name from schema
 81 |    */
 82 |   getParameter(schema: ConfigurationSchema, name: string): ConfigurationParameter | null {
 83 |     const allParams = [
 84 |       ...(schema.environmentVariables || []),
 85 |       ...(schema.arguments || []),
 86 |       ...(schema.other || [])
 87 |     ];
 88 | 
 89 |     return allParams.find(p => p.name === name) || null;
 90 |   }
 91 | 
 92 |   /**
 93 |    * Format schema for display
 94 |    */
 95 |   formatSchema(schema: ConfigurationSchema): string {
 96 |     const lines: string[] = [];
 97 | 
 98 |     if (schema.environmentVariables && schema.environmentVariables.length > 0) {
 99 |       lines.push('Environment Variables:');
100 |       schema.environmentVariables.forEach(param => {
101 |         const required = param.required ? '(required)' : '(optional)';
102 |         const sensitive = param.sensitive ? ' [sensitive]' : '';
103 |         lines.push(`  - ${param.name} ${required}${sensitive}`);
104 |         lines.push(`    ${param.description}`);
105 |         if (param.examples && param.examples.length > 0 && !param.sensitive) {
106 |           lines.push(`    Examples: ${param.examples.join(', ')}`);
107 |         }
108 |       });
109 |       lines.push('');
110 |     }
111 | 
112 |     if (schema.arguments && schema.arguments.length > 0) {
113 |       lines.push('Command Arguments:');
114 |       schema.arguments.forEach(param => {
115 |         const required = param.required ? '(required)' : '(optional)';
116 |         const multiple = param.multiple ? ' [multiple]' : '';
117 |         lines.push(`  - ${param.name} ${required}${multiple}`);
118 |         lines.push(`    ${param.description}`);
119 |         if (param.examples && param.examples.length > 0) {
120 |           lines.push(`    Examples: ${param.examples.join(', ')}`);
121 |         }
122 |       });
123 |       lines.push('');
124 |     }
125 | 
126 |     if (schema.other && schema.other.length > 0) {
127 |       lines.push('Other Configuration:');
128 |       schema.other.forEach(param => {
129 |         const required = param.required ? '(required)' : '(optional)';
130 |         lines.push(`  - ${param.name} ${required}`);
131 |         lines.push(`    ${param.description}`);
132 |       });
133 |     }
134 | 
135 |     return lines.join('\n');
136 |   }
137 | }
138 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/mcp_server_request.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: MCP Server Support Request
  2 | description: Request support for a new MCP server in NCP
  3 | title: "[MCP]: Add support for "
  4 | labels: ["mcp-server", "enhancement", "triage"]
  5 | body:
  6 |   - type: markdown
  7 |     attributes:
  8 |       value: |
  9 |         Request support for a new MCP server in NCP's discovery and management system.
 10 | 
 11 |   - type: input
 12 |     id: server-name
 13 |     attributes:
 14 |       label: MCP Server Name
 15 |       description: What is the name of the MCP server?
 16 |       placeholder: "e.g., @modelcontextprotocol/server-slack"
 17 |     validations:
 18 |       required: true
 19 | 
 20 |   - type: input
 21 |     id: repository
 22 |     attributes:
 23 |       label: Repository URL
 24 |       description: Link to the MCP server's repository
 25 |       placeholder: "https://github.com/..."
 26 |     validations:
 27 |       required: true
 28 | 
 29 |   - type: input
 30 |     id: npm-package
 31 |     attributes:
 32 |       label: NPM Package (if available)
 33 |       description: NPM package name if the server is published
 34 |       placeholder: "e.g., @modelcontextprotocol/server-slack"
 35 | 
 36 |   - type: textarea
 37 |     id: description
 38 |     attributes:
 39 |       label: Server Description
 40 |       description: What does this MCP server do?
 41 |       placeholder: Brief description of the server's functionality
 42 |     validations:
 43 |       required: true
 44 | 
 45 |   - type: dropdown
 46 |     id: category
 47 |     attributes:
 48 |       label: Server Category
 49 |       description: What category best describes this server?
 50 |       options:
 51 |         - Database (SQL, NoSQL, etc.)
 52 |         - Cloud Services (AWS, Azure, GCP)
 53 |         - Development Tools (Git, Docker, etc.)
 54 |         - Communication (Slack, Discord, etc.)
 55 |         - File System
 56 |         - Web/API Services
 57 |         - Productivity Tools
 58 |         - System Administration
 59 |         - Other
 60 |     validations:
 61 |       required: true
 62 | 
 63 |   - type: checkboxes
 64 |     id: server-status
 65 |     attributes:
 66 |       label: Server Status
 67 |       description: Please verify the server status
 68 |       options:
 69 |         - label: The server is actively maintained
 70 |           required: true
 71 |         - label: The server has clear documentation
 72 |           required: true
 73 |         - label: The server follows MCP protocol specifications
 74 |           required: true
 75 | 
 76 |   - type: textarea
 77 |     id: tools
 78 |     attributes:
 79 |       label: Available Tools
 80 |       description: List the main tools/functions this server provides
 81 |       placeholder: |
 82 |         - send_message: Send messages to channels
 83 |         - create_channel: Create new channels
 84 |         - list_channels: Get available channels
 85 | 
 86 |   - type: textarea
 87 |     id: use-cases
 88 |     attributes:
 89 |       label: Use Cases
 90 |       description: When would users want to use this MCP server?
 91 |       placeholder: |
 92 |         - Automating Slack notifications
 93 |         - Managing team communication
 94 |         - Integrating with CI/CD pipelines
 95 | 
 96 |   - type: input
 97 |     id: priority-score
 98 |     attributes:
 99 |       label: Priority Justification
100 |       description: Why should this server be prioritized?
101 |       placeholder: "Popular tool with X GitHub stars, requested by Y users, fills gap in Z domain"
102 | 
103 |   - type: textarea
104 |     id: configuration
105 |     attributes:
106 |       label: Configuration Requirements
107 |       description: What configuration is needed to use this server?
108 |       placeholder: |
109 |         Required:
110 |         - API_TOKEN: Slack bot token
111 |         - WORKSPACE_ID: Slack workspace identifier
112 | 
113 |         Optional:
114 |         - DEFAULT_CHANNEL: Default channel for messages
115 | 
116 |   - type: checkboxes
117 |     id: contribution
118 |     attributes:
119 |       label: Contribution
120 |       description: Can you help with implementation?
121 |       options:
122 |         - label: I can help test the integration
123 |         - label: I can provide configuration examples
124 |         - label: I can help with documentation
125 |         - label: I maintain or contribute to this MCP server
126 | 
127 |   - type: textarea
128 |     id: additional
129 |     attributes:
130 |       label: Additional Information
131 |       description: Any other relevant information
132 |       placeholder: Links to documentation, similar integrations, special considerations
```

--------------------------------------------------------------------------------
/test/mock-mcps/playwright-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Playwright MCP Server
  5 |  * Real MCP server structure for browser automation testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'playwright-test',
 12 |   version: '1.0.0',
 13 |   description: 'Browser automation and web scraping with cross-browser support'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'navigate_to_page',
 19 |     description: 'Navigate to web pages and URLs for automation tasks. Open websites, load web applications.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         url: {
 24 |           type: 'string',
 25 |           description: 'URL to navigate to'
 26 |         },
 27 |         wait_until: {
 28 |           type: 'string',
 29 |           description: 'Wait condition (load, domcontentloaded, networkidle)'
 30 |         },
 31 |         timeout: {
 32 |           type: 'number',
 33 |           description: 'Navigation timeout in milliseconds'
 34 |         }
 35 |       },
 36 |       required: ['url']
 37 |     }
 38 |   },
 39 |   {
 40 |     name: 'click_element',
 41 |     description: 'Click on web page elements using selectors. Click buttons, links, form elements.',
 42 |     inputSchema: {
 43 |       type: 'object',
 44 |       properties: {
 45 |         selector: {
 46 |           type: 'string',
 47 |           description: 'CSS selector or XPath for element'
 48 |         },
 49 |         button: {
 50 |           type: 'string',
 51 |           description: 'Mouse button to click (left, right, middle)'
 52 |         },
 53 |         click_count: {
 54 |           type: 'number',
 55 |           description: 'Number of clicks'
 56 |         }
 57 |       },
 58 |       required: ['selector']
 59 |     }
 60 |   },
 61 |   {
 62 |     name: 'fill_form_field',
 63 |     description: 'Fill form inputs and text fields on web pages. Enter text, complete forms, input data.',
 64 |     inputSchema: {
 65 |       type: 'object',
 66 |       properties: {
 67 |         selector: {
 68 |           type: 'string',
 69 |           description: 'CSS selector for input field'
 70 |         },
 71 |         value: {
 72 |           type: 'string',
 73 |           description: 'Text value to fill'
 74 |         },
 75 |         clear: {
 76 |           type: 'boolean',
 77 |           description: 'Clear field before filling'
 78 |         }
 79 |       },
 80 |       required: ['selector', 'value']
 81 |     }
 82 |   },
 83 |   {
 84 |     name: 'take_screenshot',
 85 |     description: 'Capture screenshots of web pages for testing and documentation. Take page screenshots, save visual evidence.',
 86 |     inputSchema: {
 87 |       type: 'object',
 88 |       properties: {
 89 |         path: {
 90 |           type: 'string',
 91 |           description: 'File path to save screenshot'
 92 |         },
 93 |         full_page: {
 94 |           type: 'boolean',
 95 |           description: 'Capture full page or just viewport'
 96 |         },
 97 |         quality: {
 98 |           type: 'number',
 99 |           description: 'JPEG quality (0-100)'
100 |         }
101 |       },
102 |       required: ['path']
103 |     }
104 |   },
105 |   {
106 |     name: 'extract_text',
107 |     description: 'Extract text content from web page elements. Scrape data, read page content, get element text.',
108 |     inputSchema: {
109 |       type: 'object',
110 |       properties: {
111 |         selector: {
112 |           type: 'string',
113 |           description: 'CSS selector for element'
114 |         },
115 |         attribute: {
116 |           type: 'string',
117 |           description: 'Optional attribute to extract instead of text'
118 |         }
119 |       },
120 |       required: ['selector']
121 |     }
122 |   },
123 |   {
124 |     name: 'wait_for_element',
125 |     description: 'Wait for elements to appear or become available on web pages. Wait for dynamic content, ensure element visibility.',
126 |     inputSchema: {
127 |       type: 'object',
128 |       properties: {
129 |         selector: {
130 |           type: 'string',
131 |           description: 'CSS selector for element to wait for'
132 |         },
133 |         state: {
134 |           type: 'string',
135 |           description: 'Element state to wait for (visible, hidden, attached)'
136 |         },
137 |         timeout: {
138 |           type: 'number',
139 |           description: 'Wait timeout in milliseconds'
140 |         }
141 |       },
142 |       required: ['selector']
143 |     }
144 |   }
145 | ];
146 | 
147 | // Create and run the server
148 | const server = new MockMCPServer(serverInfo, tools);
149 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/src/utils/security.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Security utilities for NCP
  3 |  * Handles sensitive data masking and sanitization
  4 |  */
  5 | 
  6 | /**
  7 |  * Masks sensitive information in command strings
  8 |  * Detects and masks API keys, tokens, passwords, etc.
  9 |  */
 10 | export function maskSensitiveData(text: string): string {
 11 |   if (!text) return text;
 12 | 
 13 |   let masked = text;
 14 | 
 15 |   // Mask API keys (various patterns)
 16 |   masked = masked.replace(
 17 |     /sk_test_[a-zA-Z0-9]{50,}/g,
 18 |     (match) => `sk_test_*****${match.slice(-4)}`
 19 |   );
 20 | 
 21 |   masked = masked.replace(
 22 |     /sk_live_[a-zA-Z0-9]{50,}/g,
 23 |     (match) => `sk_live_*****${match.slice(-4)}`
 24 |   );
 25 | 
 26 |   // Mask other common API key patterns
 27 |   masked = masked.replace(
 28 |     /--api-key[=\s]+([a-zA-Z0-9_-]{16,})/gi,
 29 |     (match, key) => match.replace(key, `*****${key.slice(-4)}`)
 30 |   );
 31 | 
 32 |   // Mask --key parameters
 33 |   masked = masked.replace(
 34 |     /--key[=\s]+([a-zA-Z0-9_-]{16,})/gi,
 35 |     (match, key) => match.replace(key, `*****${key.slice(-4)}`)
 36 |   );
 37 | 
 38 |   // Mask tokens
 39 |   masked = masked.replace(
 40 |     /--token[=\s]+([a-zA-Z0-9_-]{16,})/gi,
 41 |     (match, token) => match.replace(token, `*****${token.slice(-4)}`)
 42 |   );
 43 | 
 44 |   // Mask passwords
 45 |   masked = masked.replace(
 46 |     /--password[=\s]+([^\s]+)/gi,
 47 |     (match, password) => match.replace(password, '*****')
 48 |   );
 49 | 
 50 |   // Mask JWT tokens
 51 |   masked = masked.replace(
 52 |     /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
 53 |     (match) => `eyJ*****${match.slice(-4)}`
 54 |   );
 55 | 
 56 |   // Mask UUID-like keys
 57 |   masked = masked.replace(
 58 |     /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,
 59 |     (match) => `*****${match.slice(-4)}`
 60 |   );
 61 | 
 62 |   return masked;
 63 | }
 64 | 
 65 | /**
 66 |  * Formats command display with proper masking
 67 |  * @param showAsTemplates - If true, shows template variables like {{API_KEY}} instead of masked values
 68 |  */
 69 | export function formatCommandDisplay(command: string, args: string[] = [], showAsTemplates: boolean = true): string {
 70 |   const fullCommand = `${command} ${args.join(' ')}`.trim();
 71 | 
 72 |   if (showAsTemplates) {
 73 |     return maskSensitiveDataAsTemplates(fullCommand);
 74 |   }
 75 |   return maskSensitiveData(fullCommand);
 76 | }
 77 | 
 78 | /**
 79 |  * Masks sensitive data by replacing with template variable names
 80 |  * This provides cleaner display without exposing any part of secrets
 81 |  */
 82 | export function maskSensitiveDataAsTemplates(text: string): string {
 83 |   if (!text) return text;
 84 | 
 85 |   let masked = text;
 86 | 
 87 |   // Replace API key parameters
 88 |   masked = masked.replace(
 89 |     /--api-key[=\s]+([^\s]+)/gi,
 90 |     '--api-key={{API_KEY}}'
 91 |   );
 92 | 
 93 |   // Replace key parameters (like Upstash keys)
 94 |   masked = masked.replace(
 95 |     /--key[=\s]+([^\s]+)/gi,
 96 |     '--key={{API_KEY}}'
 97 |   );
 98 | 
 99 |   // Replace token parameters
100 |   masked = masked.replace(
101 |     /--token[=\s]+([^\s]+)/gi,
102 |     '--token={{TOKEN}}'
103 |   );
104 | 
105 |   // Replace OAuth tokens
106 |   masked = masked.replace(
107 |     /--oauth-token[=\s]+([^\s]+)/gi,
108 |     '--oauth-token={{OAUTH_TOKEN}}'
109 |   );
110 | 
111 |   // Replace password parameters
112 |   masked = masked.replace(
113 |     /--password[=\s]+([^\s]+)/gi,
114 |     '--password={{PASSWORD}}'
115 |   );
116 | 
117 |   // Replace secret parameters
118 |   masked = masked.replace(
119 |     /--secret[=\s]+([^\s]+)/gi,
120 |     '--secret={{SECRET}}'
121 |   );
122 | 
123 |   // Replace auth parameters
124 |   masked = masked.replace(
125 |     /--auth[=\s]+([^\s]+)/gi,
126 |     '--auth={{AUTH}}'
127 |   );
128 | 
129 |   // Replace environment variable references that look like they contain secrets
130 |   masked = masked.replace(
131 |     /\$\{?([A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASS|PWD|API|AUTH)[A-Z_]*)\}?/g,
132 |     '{{$1}}'
133 |   );
134 | 
135 |   return masked;
136 | }
137 | 
138 | /**
139 |  * Checks if a string contains sensitive data patterns
140 |  */
141 | export function containsSensitiveData(text: string): boolean {
142 |   if (!text) return false;
143 | 
144 |   const sensitivePatterns = [
145 |     /sk_test_[a-zA-Z0-9]{99}/,
146 |     /sk_live_[a-zA-Z0-9]{99}/,
147 |     /--api-key[=\s]+[a-zA-Z0-9_-]{16,}/i,
148 |     /--token[=\s]+[a-zA-Z0-9_-]{16,}/i,
149 |     /--password[=\s]+[^\s]+/i,
150 |     /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/,
151 |     /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
152 |   ];
153 | 
154 |   return sensitivePatterns.some(pattern => pattern.test(text));
155 | }
```

--------------------------------------------------------------------------------
/src/utils/runtime-detector.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Runtime Detector
  3 |  *
  4 |  * Detects which runtime (bundled vs system) NCP is currently running with.
  5 |  * This is detected fresh on every boot to respect Claude Desktop's dynamic settings.
  6 |  */
  7 | 
  8 | import { existsSync } from 'fs';
  9 | import { getBundledRuntimePath } from './client-registry.js';
 10 | 
 11 | export interface RuntimeInfo {
 12 |   /** The runtime being used ('bundled' or 'system') */
 13 |   type: 'bundled' | 'system';
 14 | 
 15 |   /** Path to Node.js runtime to use */
 16 |   nodePath: string;
 17 | 
 18 |   /** Path to Python runtime to use (if available) */
 19 |   pythonPath?: string;
 20 | }
 21 | 
 22 | /**
 23 |  * Detect which runtime NCP is currently running with.
 24 |  *
 25 |  * Strategy:
 26 |  * 1. Check process.execPath (how NCP was launched)
 27 |  * 2. Compare with known bundled runtime paths
 28 |  * 3. If match → we're running via bundled runtime
 29 |  * 4. If no match → we're running via system runtime
 30 |  */
 31 | export function detectRuntime(): RuntimeInfo {
 32 |   const currentNodePath = process.execPath;
 33 | 
 34 |   // Check if we're running via Claude Desktop's bundled Node
 35 |   const claudeBundledNode = getBundledRuntimePath('claude-desktop', 'node');
 36 |   const claudeBundledPython = getBundledRuntimePath('claude-desktop', 'python');
 37 | 
 38 |   // If our execPath matches the bundled Node path, we're running via bundled runtime
 39 |   if (claudeBundledNode && currentNodePath === claudeBundledNode) {
 40 |     return {
 41 |       type: 'bundled',
 42 |       nodePath: claudeBundledNode,
 43 |       pythonPath: claudeBundledPython || undefined
 44 |     };
 45 |   }
 46 | 
 47 |   // Check if execPath is inside Claude.app (might be different bundled path)
 48 |   const isInsideClaudeApp = currentNodePath.includes('/Claude.app/') ||
 49 |                             currentNodePath.includes('\\Claude\\') ||
 50 |                             currentNodePath.includes('/Claude/');
 51 | 
 52 |   if (isInsideClaudeApp && claudeBundledNode && existsSync(claudeBundledNode)) {
 53 |     // We're running from Claude Desktop, use its bundled runtimes
 54 |     return {
 55 |       type: 'bundled',
 56 |       nodePath: claudeBundledNode,
 57 |       pythonPath: claudeBundledPython || undefined
 58 |     };
 59 |   }
 60 | 
 61 |   // Otherwise, we're running via system runtime
 62 |   return {
 63 |     type: 'system',
 64 |     nodePath: 'node', // Use system node
 65 |     pythonPath: 'python3' // Use system python
 66 |   };
 67 | }
 68 | 
 69 | /**
 70 |  * Get runtime to use for spawning .dxt extension processes.
 71 |  * Uses the same runtime that NCP itself is running with.
 72 |  */
 73 | export function getRuntimeForExtension(command: string): string {
 74 |   const runtime = detectRuntime();
 75 | 
 76 |   // If command is 'node' or ends with '/node', use detected Node runtime
 77 |   if (command === 'node' || command.endsWith('/node') || command.endsWith('\\node.exe')) {
 78 |     return runtime.nodePath;
 79 |   }
 80 | 
 81 |   // If command is 'npx', use npx from detected Node runtime
 82 |   if (command === 'npx' || command.endsWith('/npx') || command.endsWith('\\npx.cmd')) {
 83 |     // If using bundled runtime, construct npx path from node path
 84 |     if (runtime.type === 'bundled') {
 85 |       // Bundled node path: /Applications/Claude.app/.../node
 86 |       // Bundled npx path: /Applications/Claude.app/.../npx
 87 |       const npxPath = runtime.nodePath.replace(/\/node$/, '/npx').replace(/\\node\.exe$/, '\\npx.cmd');
 88 |       return npxPath;
 89 |     }
 90 |     // For system runtime, use system npx
 91 |     return 'npx';
 92 |   }
 93 | 
 94 |   // If command is 'python3'/'python', use detected Python runtime
 95 |   if (command === 'python3' || command === 'python' ||
 96 |       command.endsWith('/python3') || command.endsWith('/python') ||
 97 |       command.endsWith('\\python.exe') || command.endsWith('\\python3.exe')) {
 98 |     return runtime.pythonPath || command; // Fallback to original if no Python detected
 99 |   }
100 | 
101 |   // For other commands, return as-is
102 |   return command;
103 | }
104 | 
105 | /**
106 |  * Log runtime detection info for debugging
107 |  */
108 | export function logRuntimeInfo(): void {
109 |   const runtime = detectRuntime();
110 |   console.log(`[Runtime Detection]`);
111 |   console.log(`  Type: ${runtime.type}`);
112 |   console.log(`  Node: ${runtime.nodePath}`);
113 |   if (runtime.pythonPath) {
114 |     console.log(`  Python: ${runtime.pythonPath}`);
115 |   }
116 |   console.log(`  Process execPath: ${process.execPath}`);
117 | }
118 | 
```

--------------------------------------------------------------------------------
/test/mcp-immediate-response-check.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Test MCP Server Immediate Response
  4 |  *
  5 |  * Verifies that NCP MCP server:
  6 |  * 1. Responds to initialize immediately
  7 |  * 2. Responds to tools/list immediately (without waiting for indexing)
  8 |  * 3. Advertises 'find' and 'run' tools
  9 |  * 4. Does not block on indexing
 10 |  */
 11 | 
 12 | import { MCPServer } from '../dist/server/mcp-server.js';
 13 | 
 14 | async function testMCPImmediateResponse() {
 15 |   console.log('========================================');
 16 |   console.log('Testing MCP Server Immediate Response');
 17 |   console.log('========================================\n');
 18 | 
 19 |   const server = new MCPServer('default', false, false); // No progress output
 20 | 
 21 |   // Test 1: Initialize should return immediately
 22 |   console.log('Test 1: Initialize returns immediately');
 23 |   console.log('----------------------------------------');
 24 |   const initStartTime = Date.now();
 25 |   await server.initialize();
 26 |   const initDuration = Date.now() - initStartTime;
 27 | 
 28 |   if (initDuration < 100) {
 29 |     console.log(`✓ PASS: Initialize returned in ${initDuration}ms (non-blocking)`);
 30 |   } else {
 31 |     console.log(`✗ FAIL: Initialize took ${initDuration}ms (should be < 100ms)`);
 32 |   }
 33 |   console.log('');
 34 | 
 35 |   // Test 2: tools/list should return immediately
 36 |   console.log('Test 2: tools/list returns immediately (even during indexing)');
 37 |   console.log('----------------------------------------');
 38 |   const listStartTime = Date.now();
 39 |   const toolsResponse = await server.handleRequest({
 40 |     jsonrpc: '2.0',
 41 |     id: 1,
 42 |     method: 'tools/list'
 43 |   });
 44 |   const listDuration = Date.now() - listStartTime;
 45 | 
 46 |   if (listDuration < 100) {
 47 |     console.log(`✓ PASS: tools/list returned in ${listDuration}ms (non-blocking)`);
 48 |   } else {
 49 |     console.log(`✗ FAIL: tools/list took ${listDuration}ms (should be < 100ms)`);
 50 |   }
 51 |   console.log('');
 52 | 
 53 |   // Test 3: Verify tools are advertised
 54 |   console.log('Test 3: Advertises find and run tools');
 55 |   console.log('----------------------------------------');
 56 |   const tools = toolsResponse.result?.tools || [];
 57 |   const toolNames = tools.map(t => t.name);
 58 | 
 59 |   console.log(`Found ${tools.length} tools: ${toolNames.join(', ')}`);
 60 | 
 61 |   if (toolNames.includes('find')) {
 62 |     console.log('✓ PASS: find tool advertised');
 63 |   } else {
 64 |     console.log('✗ FAIL: find tool NOT advertised');
 65 |   }
 66 | 
 67 |   if (toolNames.includes('run')) {
 68 |     console.log('✓ PASS: run tool advertised');
 69 |   } else {
 70 |     console.log('✗ FAIL: run tool NOT advertised');
 71 |   }
 72 |   console.log('');
 73 | 
 74 |   // Test 4: Initialize request returns immediately
 75 |   console.log('Test 4: Initialize request returns immediately');
 76 |   console.log('----------------------------------------');
 77 |   const initReqStartTime = Date.now();
 78 |   const initResponse = await server.handleRequest({
 79 |     jsonrpc: '2.0',
 80 |     id: 2,
 81 |     method: 'initialize',
 82 |     params: {
 83 |       protocolVersion: '2024-11-05',
 84 |       capabilities: {},
 85 |       clientInfo: { name: 'test-client', version: '1.0.0' }
 86 |     }
 87 |   });
 88 |   const initReqDuration = Date.now() - initReqStartTime;
 89 | 
 90 |   if (initReqDuration < 50) {
 91 |     console.log(`✓ PASS: Initialize request returned in ${initReqDuration}ms`);
 92 |   } else {
 93 |     console.log(`✗ FAIL: Initialize request took ${initReqDuration}ms (should be < 50ms)`);
 94 |   }
 95 | 
 96 |   if (initResponse.result?.protocolVersion) {
 97 |     console.log(`✓ PASS: Initialize returned protocol version ${initResponse.result.protocolVersion}`);
 98 |   } else {
 99 |     console.log('✗ FAIL: Initialize did not return protocol version');
100 |   }
101 |   console.log('');
102 | 
103 |   await server.cleanup();
104 | 
105 |   console.log('========================================');
106 |   console.log('Test Summary');
107 |   console.log('========================================');
108 |   console.log('Expected behavior:');
109 |   console.log('  - initialize() returns immediately (< 100ms)');
110 |   console.log('  - tools/list returns immediately (< 100ms)');
111 |   console.log('  - Advertises find and run tools');
112 |   console.log('  - Indexing happens in background');
113 | }
114 | 
115 | testMCPImmediateResponse().catch(error => {
116 |   console.error('Test failed with error:', error);
117 |   process.exit(1);
118 | });
119 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "@portel/ncp",
  3 |   "version": "1.5.2",
  4 |   "mcpName": "io.github.portel-dev/ncp",
  5 |   "description": "Natural Context Provider - N-to-1 MCP Orchestration for AI Assistants",
  6 |   "main": "dist/index.js",
  7 |   "module": "dist/index.js",
  8 |   "exports": {
  9 |     ".": {
 10 |       "types": "./dist/index.d.ts",
 11 |       "import": "./dist/index.js"
 12 |     }
 13 |   },
 14 |   "bin": {
 15 |     "ncp": "dist/index.js"
 16 |   },
 17 |   "type": "module",
 18 |   "scripts": {
 19 |     "build": "tsc && chmod +x dist/index.js",
 20 |     "postinstall": "npm run build:if-dev",
 21 |     "build:if-dev": "[ -d node_modules/typescript ] && npm run build || echo 'Build skipped (production install)'",
 22 |     "start": "node dist/index.js",
 23 |     "dev": "npm run build && npm run start",
 24 |     "test": "jest --detectOpenHandles --forceExit",
 25 |     "test:coverage": "jest --coverage --detectOpenHandles --forceExit",
 26 |     "test:watch": "jest --watch",
 27 |     "test:critical": "jest test/mcp-server-protocol.test.ts test/mcp-timeout-scenarios.test.ts --verbose --detectOpenHandles --forceExit",
 28 |     "test:integration": "npm run build && node test/integration/mcp-client-simulation.test.cjs",
 29 |     "test:cli": "bash test/cli-help-validation.sh",
 30 |     "test:pre-publish": "npm run test:critical && npm run test:integration",
 31 |     "test:package": "node scripts/test-package-locally.cjs",
 32 |     "build:dxt": "npm run build && npm prune --production && npx @anthropic-ai/mcpb pack && npm run rename:dxt && npm install",
 33 |     "rename:dxt": "for f in *.mcpb; do [ -f \"$f\" ] && mv \"$f\" \"${f%.mcpb}.dxt\" || true; done",
 34 |     "stats": "node scripts/check-dxt-downloads.js",
 35 |     "prepack": "npm run build && npm run test:pre-publish",
 36 |     "prepublishOnly": "npm run build && npm run test:pre-publish && node scripts/sync-server-version.cjs",
 37 |     "release": "release-it",
 38 |     "release:dry": "release-it --dry-run"
 39 |   },
 40 |   "keywords": [
 41 |     "mcp",
 42 |     "model-context-protocol",
 43 |     "ai-orchestration",
 44 |     "tool-discovery"
 45 |   ],
 46 |   "author": "Portel",
 47 |   "license": "Elastic-2.0",
 48 |   "repository": {
 49 |     "type": "git",
 50 |     "url": "git+https://github.com/portel-dev/ncp.git"
 51 |   },
 52 |   "homepage": "https://github.com/portel-dev/ncp#readme",
 53 |   "bugs": {
 54 |     "url": "https://github.com/portel-dev/ncp/issues"
 55 |   },
 56 |   "types": "dist/index.d.ts",
 57 |   "engines": {
 58 |     "node": ">=18.0.0"
 59 |   },
 60 |   "files": [
 61 |     "dist",
 62 |     "LICENSE",
 63 |     "README.md",
 64 |     "server.json",
 65 |     "package.json"
 66 |   ],
 67 |   "files:comments": "Explicit list of files to include in the package",
 68 |   "dependencies": {
 69 |     "@modelcontextprotocol/sdk": "^1.18.0",
 70 |     "@types/prompts": "^2.4.9",
 71 |     "@xenova/transformers": "^2.17.2",
 72 |     "asciichart": "^1.5.25",
 73 |     "chalk": "^5.3.0",
 74 |     "cli-graph": "^3.2.2",
 75 |     "cli-highlight": "^2.1.11",
 76 |     "clipboardy": "^4.0.0",
 77 |     "commander": "^14.0.1",
 78 |     "env-paths": "^3.0.0",
 79 |     "json-colorizer": "^3.0.1",
 80 |     "marked": "^15.0.12",
 81 |     "marked-terminal": "^7.3.0",
 82 |     "prettyjson": "^1.2.5",
 83 |     "prompts": "^2.4.2",
 84 |     "yaml": "^2.8.1"
 85 |   },
 86 |   "devDependencies": {
 87 |     "@agent-infra/mcp-server-browser": "^1.2.23",
 88 |     "@amap/amap-maps-mcp-server": "^0.0.8",
 89 |     "@anthropic-ai/mcpb": "^1.1.1",
 90 |     "@apify/actors-mcp-server": "^0.4.15",
 91 |     "@benborla29/mcp-server-mysql": "^2.0.5",
 92 |     "@currents/mcp": "^2.0.0",
 93 |     "@heroku/mcp-server": "^1.0.7",
 94 |     "@hubspot/mcp-server": "^0.4.0",
 95 |     "@modelcontextprotocol/server-filesystem": "^2025.8.21",
 96 |     "@modelcontextprotocol/server-sequential-thinking": "^2025.7.1",
 97 |     "@notionhq/notion-mcp-server": "^1.9.0",
 98 |     "@release-it/conventional-changelog": "^10.0.1",
 99 |     "@supabase/mcp-server-supabase": "^0.5.5",
100 |     "@types/express": "^5.0.3",
101 |     "@types/jest": "^30.0.0",
102 |     "@types/marked-terminal": "^6.1.1",
103 |     "@types/node": "^20.0.0",
104 |     "@types/prettyjson": "^0.0.33",
105 |     "@upstash/context7-mcp": "^1.0.17",
106 |     "@winor30/mcp-server-datadog": "^1.6.0",
107 |     "jest": "^30.1.3",
108 |     "mcp-hello-world": "^1.1.2",
109 |     "mcp-server": "^0.0.9",
110 |     "mcp-server-kubernetes": "^2.9.6",
111 |     "release-it": "^19.0.4",
112 |     "ts-jest": "^29.4.2",
113 |     "tsx": "^4.20.5",
114 |     "typescript": "^5.0.0"
115 |   }
116 | }
117 | 
```

--------------------------------------------------------------------------------
/test/mock-mcps/shell-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Shell MCP Server
  5 |  * Real MCP server structure for shell command execution testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'shell-test',
 12 |   version: '1.0.0',
 13 |   description: 'Execute shell commands and system operations including scripts, processes, and system management'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'execute_command',
 19 |     description: 'Execute shell commands and system operations. Run scripts, manage processes, perform system tasks.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         command: {
 24 |           type: 'string',
 25 |           description: 'Shell command to execute'
 26 |         },
 27 |         working_directory: {
 28 |           type: 'string',
 29 |           description: 'Working directory for command execution'
 30 |         },
 31 |         timeout: {
 32 |           type: 'number',
 33 |           description: 'Command timeout in seconds'
 34 |         },
 35 |         environment: {
 36 |           type: 'object',
 37 |           description: 'Environment variables for command'
 38 |         },
 39 |         capture_output: {
 40 |           type: 'boolean',
 41 |           description: 'Capture command output'
 42 |         }
 43 |       },
 44 |       required: ['command']
 45 |     }
 46 |   },
 47 |   {
 48 |     name: 'run_script',
 49 |     description: 'Execute shell scripts and batch operations with parameters. Run automation scripts, execute batch jobs.',
 50 |     inputSchema: {
 51 |       type: 'object',
 52 |       properties: {
 53 |         script_path: {
 54 |           type: 'string',
 55 |           description: 'Path to script file'
 56 |         },
 57 |         arguments: {
 58 |           type: 'array',
 59 |           description: 'Script arguments',
 60 |           items: { type: 'string' }
 61 |         },
 62 |         interpreter: {
 63 |           type: 'string',
 64 |           description: 'Script interpreter (bash, python, node, etc.)'
 65 |         },
 66 |         working_directory: {
 67 |           type: 'string',
 68 |           description: 'Working directory for script'
 69 |         }
 70 |       },
 71 |       required: ['script_path']
 72 |     }
 73 |   },
 74 |   {
 75 |     name: 'manage_process',
 76 |     description: 'Manage system processes including start, stop, and monitoring. Control services, manage applications.',
 77 |     inputSchema: {
 78 |       type: 'object',
 79 |       properties: {
 80 |         action: {
 81 |           type: 'string',
 82 |           description: 'Process action (start, stop, restart, status, list)'
 83 |         },
 84 |         process_name: {
 85 |           type: 'string',
 86 |           description: 'Process or service name'
 87 |         },
 88 |         pid: {
 89 |           type: 'number',
 90 |           description: 'Process ID for specific process operations'
 91 |         },
 92 |         signal: {
 93 |           type: 'string',
 94 |           description: 'Signal to send to process (TERM, KILL, etc.)'
 95 |         }
 96 |       },
 97 |       required: ['action']
 98 |     }
 99 |   },
100 |   {
101 |     name: 'check_system_info',
102 |     description: 'Get system information including resources, processes, and status. Monitor system health, check resources.',
103 |     inputSchema: {
104 |       type: 'object',
105 |       properties: {
106 |         info_type: {
107 |           type: 'string',
108 |           description: 'Type of system info (cpu, memory, disk, network, processes)'
109 |         },
110 |         detailed: {
111 |           type: 'boolean',
112 |           description: 'Include detailed information'
113 |         }
114 |       },
115 |       required: ['info_type']
116 |     }
117 |   },
118 |   {
119 |     name: 'manage_environment',
120 |     description: 'Manage environment variables and system configuration. Set variables, configure system settings.',
121 |     inputSchema: {
122 |       type: 'object',
123 |       properties: {
124 |         action: {
125 |           type: 'string',
126 |           description: 'Environment action (get, set, unset, list)'
127 |         },
128 |         variable: {
129 |           type: 'string',
130 |           description: 'Environment variable name'
131 |         },
132 |         value: {
133 |           type: 'string',
134 |           description: 'Variable value for set action'
135 |         },
136 |         scope: {
137 |           type: 'string',
138 |           description: 'Variable scope (session, user, system)'
139 |         }
140 |       },
141 |       required: ['action']
142 |     }
143 |   }
144 | ];
145 | 
146 | // Create and run the server
147 | const server = new MockMCPServer(serverInfo, tools);
148 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/test/mock-mcps/brave-search-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Brave Search MCP Server
  5 |  * Real MCP server structure for Brave Search API testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'brave-search-test',
 12 |   version: '1.0.0',
 13 |   description: 'Web search capabilities with privacy-focused results and real-time information'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'web_search',
 19 |     description: 'Search the web using Brave Search API with privacy protection. Find information, research topics, get current data.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         query: {
 24 |           type: 'string',
 25 |           description: 'Search query string'
 26 |         },
 27 |         count: {
 28 |           type: 'number',
 29 |           description: 'Number of results to return'
 30 |         },
 31 |         offset: {
 32 |           type: 'number',
 33 |           description: 'Result offset for pagination'
 34 |         },
 35 |         country: {
 36 |           type: 'string',
 37 |           description: 'Country code for localized results'
 38 |         },
 39 |         search_lang: {
 40 |           type: 'string',
 41 |           description: 'Search language code'
 42 |         },
 43 |         ui_lang: {
 44 |           type: 'string',
 45 |           description: 'UI language code'
 46 |         },
 47 |         freshness: {
 48 |           type: 'string',
 49 |           description: 'Result freshness (pd, pw, pm, py for past day/week/month/year)'
 50 |         }
 51 |       },
 52 |       required: ['query']
 53 |     }
 54 |   },
 55 |   {
 56 |     name: 'news_search',
 57 |     description: 'Search for news articles with current events and breaking news. Get latest news, find articles, track stories.',
 58 |     inputSchema: {
 59 |       type: 'object',
 60 |       properties: {
 61 |         query: {
 62 |           type: 'string',
 63 |           description: 'News search query'
 64 |         },
 65 |         count: {
 66 |           type: 'number',
 67 |           description: 'Number of news results'
 68 |         },
 69 |         offset: {
 70 |           type: 'number',
 71 |           description: 'Result offset'
 72 |         },
 73 |         freshness: {
 74 |           type: 'string',
 75 |           description: 'News freshness filter'
 76 |         },
 77 |         text_decorations: {
 78 |           type: 'boolean',
 79 |           description: 'Include text decorations in results'
 80 |         }
 81 |       },
 82 |       required: ['query']
 83 |     }
 84 |   },
 85 |   {
 86 |     name: 'image_search',
 87 |     description: 'Search for images with filtering options. Find pictures, locate visual content, discover graphics.',
 88 |     inputSchema: {
 89 |       type: 'object',
 90 |       properties: {
 91 |         query: {
 92 |           type: 'string',
 93 |           description: 'Image search query'
 94 |         },
 95 |         count: {
 96 |           type: 'number',
 97 |           description: 'Number of image results'
 98 |         },
 99 |         offset: {
100 |           type: 'number',
101 |           description: 'Result offset'
102 |         },
103 |         size: {
104 |           type: 'string',
105 |           description: 'Image size filter (small, medium, large, wallpaper)'
106 |         },
107 |         color: {
108 |           type: 'string',
109 |           description: 'Color filter'
110 |         },
111 |         type: {
112 |           type: 'string',
113 |           description: 'Image type (photo, clipart, lineart, animated)'
114 |         },
115 |         layout: {
116 |           type: 'string',
117 |           description: 'Image layout (square, wide, tall)'
118 |         }
119 |       },
120 |       required: ['query']
121 |     }
122 |   },
123 |   {
124 |     name: 'video_search',
125 |     description: 'Search for videos across platforms with filtering capabilities. Find educational content, tutorials, entertainment.',
126 |     inputSchema: {
127 |       type: 'object',
128 |       properties: {
129 |         query: {
130 |           type: 'string',
131 |           description: 'Video search query'
132 |         },
133 |         count: {
134 |           type: 'number',
135 |           description: 'Number of video results'
136 |         },
137 |         offset: {
138 |           type: 'number',
139 |           description: 'Result offset'
140 |         },
141 |         duration: {
142 |           type: 'string',
143 |           description: 'Video duration filter (short, medium, long)'
144 |         },
145 |         resolution: {
146 |           type: 'string',
147 |           description: 'Video resolution filter'
148 |         }
149 |       },
150 |       required: ['query']
151 |     }
152 |   }
153 | ];
154 | 
155 | // Create and run the server
156 | const server = new MockMCPServer(serverInfo, tools);
157 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/test/mock-mcps/docker-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Docker MCP Server
  5 |  * Real MCP server structure for Docker container management testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'docker-test',
 12 |   version: '1.0.0',
 13 |   description: 'Container management including Docker operations, image building, and deployment'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'run_container',
 19 |     description: 'Run Docker containers from images with configuration options. Deploy applications, start services.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         image: {
 24 |           type: 'string',
 25 |           description: 'Docker image name and tag'
 26 |         },
 27 |         name: {
 28 |           type: 'string',
 29 |           description: 'Container name'
 30 |         },
 31 |         ports: {
 32 |           type: 'array',
 33 |           description: 'Port mappings (host:container)',
 34 |           items: { type: 'string' }
 35 |         },
 36 |         volumes: {
 37 |           type: 'array',
 38 |           description: 'Volume mappings',
 39 |           items: { type: 'string' }
 40 |         },
 41 |         environment: {
 42 |           type: 'object',
 43 |           description: 'Environment variables'
 44 |         },
 45 |         detached: {
 46 |           type: 'boolean',
 47 |           description: 'Run container in background'
 48 |         }
 49 |       },
 50 |       required: ['image']
 51 |     }
 52 |   },
 53 |   {
 54 |     name: 'build_image',
 55 |     description: 'Build Docker images from Dockerfile with build context. Create custom images, package applications.',
 56 |     inputSchema: {
 57 |       type: 'object',
 58 |       properties: {
 59 |         dockerfile_path: {
 60 |           type: 'string',
 61 |           description: 'Path to Dockerfile'
 62 |         },
 63 |         context_path: {
 64 |           type: 'string',
 65 |           description: 'Build context directory'
 66 |         },
 67 |         tag: {
 68 |           type: 'string',
 69 |           description: 'Image tag name'
 70 |         },
 71 |         build_args: {
 72 |           type: 'object',
 73 |           description: 'Build arguments'
 74 |         },
 75 |         no_cache: {
 76 |           type: 'boolean',
 77 |           description: 'Build without cache'
 78 |         }
 79 |       },
 80 |       required: ['dockerfile_path', 'tag']
 81 |     }
 82 |   },
 83 |   {
 84 |     name: 'manage_container',
 85 |     description: 'Manage Docker container lifecycle including start, stop, restart operations. Control running containers.',
 86 |     inputSchema: {
 87 |       type: 'object',
 88 |       properties: {
 89 |         action: {
 90 |           type: 'string',
 91 |           description: 'Container action (start, stop, restart, remove, pause, unpause)'
 92 |         },
 93 |         container: {
 94 |           type: 'string',
 95 |           description: 'Container name or ID'
 96 |         },
 97 |         force: {
 98 |           type: 'boolean',
 99 |           description: 'Force action if needed'
100 |         }
101 |       },
102 |       required: ['action', 'container']
103 |     }
104 |   },
105 |   {
106 |     name: 'list_containers',
107 |     description: 'List Docker containers with filtering and status information. View running containers, check container status.',
108 |     inputSchema: {
109 |       type: 'object',
110 |       properties: {
111 |         all: {
112 |           type: 'boolean',
113 |           description: 'Include stopped containers'
114 |         },
115 |         filter: {
116 |           type: 'object',
117 |           description: 'Filter criteria'
118 |         },
119 |         format: {
120 |           type: 'string',
121 |           description: 'Output format'
122 |         }
123 |       }
124 |     }
125 |   },
126 |   {
127 |     name: 'execute_in_container',
128 |     description: 'Execute commands inside running Docker containers. Debug containers, run maintenance tasks.',
129 |     inputSchema: {
130 |       type: 'object',
131 |       properties: {
132 |         container: {
133 |           type: 'string',
134 |           description: 'Container name or ID'
135 |         },
136 |         command: {
137 |           type: 'string',
138 |           description: 'Command to execute'
139 |         },
140 |         interactive: {
141 |           type: 'boolean',
142 |           description: 'Interactive mode'
143 |         },
144 |         tty: {
145 |           type: 'boolean',
146 |           description: 'Allocate pseudo-TTY'
147 |         },
148 |         user: {
149 |           type: 'string',
150 |           description: 'User to run command as'
151 |         }
152 |       },
153 |       required: ['container', 'command']
154 |     }
155 |   }
156 | ];
157 | 
158 | // Create and run the server
159 | const server = new MockMCPServer(serverInfo, tools);
160 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Release Process
  2 | 
  3 | This document describes how to release a new version of NCP.
  4 | 
  5 | ## Automated Release via GitHub Actions
  6 | 
  7 | ### Prerequisites
  8 | 
  9 | 1. All changes committed and pushed to `main`
 10 | 2. All tests passing
 11 | 3. Clean working directory
 12 | 
 13 | ### Release Steps
 14 | 
 15 | 1. **Trigger Release Workflow**
 16 |    - Go to Actions → Release workflow
 17 |    - Click "Run workflow"
 18 |    - Select release type: `patch`, `minor`, or `major`
 19 |    - Optionally check "Dry run" to test without publishing
 20 | 
 21 | 2. **What Happens Automatically**
 22 | 
 23 |    The release workflow (`release.yml`) will:
 24 |    - ✅ Run full test suite
 25 |    - ✅ Build the project
 26 |    - ✅ Bump version in `package.json`
 27 |    - ✅ Update `CHANGELOG.md` with conventional commits
 28 |    - ✅ Create git tag (e.g., `1.4.0`)
 29 |    - ✅ Push tag to GitHub
 30 |    - ✅ Create GitHub Release
 31 |    - ✅ Publish to NPM (`@portel/ncp`)
 32 | 
 33 | 3. **MCP Registry Publication** (Automatic)
 34 | 
 35 |    After GitHub Release is published, the MCP registry workflow (`publish-mcp-registry.yml`) automatically:
 36 |    - ✅ Syncs version to `server.json`
 37 |    - ✅ Validates `server.json` against MCP schema
 38 |    - ✅ Downloads MCP Publisher CLI
 39 |    - ✅ Authenticates via GitHub OIDC (no secrets needed!)
 40 |    - ✅ Publishes to MCP Registry
 41 | 
 42 |    **Registry Details**:
 43 |    - Package: `io.github.portel-dev/ncp`
 44 |    - Authentication: GitHub OIDC (automatic via `id-token: write` permission)
 45 |    - No manual steps required!
 46 | 
 47 | ## Manual Release (Not Recommended)
 48 | 
 49 | If you need to release manually:
 50 | 
 51 | ```bash
 52 | # Ensure clean state
 53 | git status
 54 | 
 55 | # Run release-it
 56 | npm run release
 57 | 
 58 | # This will:
 59 | # - Prompt for version bump type
 60 | # - Run tests
 61 | # - Update version and CHANGELOG
 62 | # - Create git tag
 63 | # - Push to GitHub
 64 | # - Publish to NPM
 65 | 
 66 | # MCP Registry will auto-publish when GitHub Release is created
 67 | ```
 68 | 
 69 | ## Version Numbering
 70 | 
 71 | We follow [Semantic Versioning](https://semver.org/):
 72 | 
 73 | - **Major** (X.0.0): Breaking changes
 74 | - **Minor** (x.X.0): New features (backward compatible)
 75 | - **Patch** (x.x.X): Bug fixes (backward compatible)
 76 | 
 77 | ## Post-Release Checklist
 78 | 
 79 | After release completes:
 80 | 
 81 | - [ ] Verify NPM package: https://www.npmjs.com/package/@portel/ncp
 82 | - [ ] Check GitHub Release: https://github.com/portel-dev/ncp/releases
 83 | - [ ] Verify MCP Registry listing (may take a few minutes)
 84 | - [ ] Test installation: `npx @portel/ncp@latest --version`
 85 | - [ ] Announce release (if significant)
 86 | 
 87 | ## Troubleshooting
 88 | 
 89 | ### NPM Publish Failed
 90 | - Check NPM authentication in GitHub Secrets
 91 | - Verify package name isn't taken
 92 | - Check `.npmignore` for correct file exclusions
 93 | 
 94 | ### MCP Registry Publish Failed
 95 | 
 96 | **Issue: Organization not detected with OIDC**
 97 | 
 98 | If you see "portel-dev organization not detected" with GitHub OIDC:
 99 | 
100 | 1. **Use GitHub Personal Access Token (Recommended)**:
101 |    - Create a GitHub PAT with `repo` and `read:org` scopes
102 |    - Go to: Settings → Developer settings → Personal access tokens → Tokens (classic)
103 |    - Generate new token with scopes: `repo`, `read:org`
104 |    - Add as GitHub Secret: `Settings → Secrets → Actions → New repository secret`
105 |    - Name: `MCP_GITHUB_TOKEN`
106 |    - Value: Your PAT
107 |    - Workflow will automatically fallback to PAT if OIDC fails
108 | 
109 | 2. **Other troubleshooting**:
110 |    - Check GitHub Actions logs for `publish-mcp-registry` workflow
111 |    - Verify `server.json` is valid (run `jsonschema -i server.json /tmp/mcp-server.schema.json`)
112 |    - Ensure `id-token: write` permission is set in workflow
113 |    - Confirm you're an admin of `portel-dev` organization: `gh api orgs/portel-dev/memberships/$(gh api user -q .login)`
114 | 
115 | ### Release Workflow Failed
116 | - Check test failures in Actions logs
117 | - Ensure clean working directory
118 | - Verify all dependencies are installed
119 | 
120 | ## Emergency Hotfix Process
121 | 
122 | For critical bugs requiring immediate release:
123 | 
124 | 1. Create hotfix branch from affected release tag
125 | 2. Fix the bug
126 | 3. Follow normal release process with `patch` bump
127 | 4. Both NPM and MCP Registry will auto-publish
128 | 
129 | ## Release Artifacts
130 | 
131 | Each release produces:
132 | 
133 | - **NPM Package**: `@portel/[email protected]` on npmjs.com
134 | - **MCP Registry Entry**: `io.github.portel-dev/ncp` in MCP registry
135 | - **GitHub Release**: Tagged release with changelog
136 | - **Git Tag**: `X.Y.Z` (or `vX.Y.Z` format)
137 | 
138 | ## Contact
139 | 
140 | For release issues or questions:
141 | - GitHub Issues: https://github.com/portel-dev/ncp/issues
142 | - Repository: https://github.com/portel-dev/ncp
143 | 
```

--------------------------------------------------------------------------------
/docs/download-stats.md:
--------------------------------------------------------------------------------

```markdown
  1 | # NCP Download Statistics
  2 | 
  3 | **Last Updated:** Auto-updated by GitHub badges
  4 | 
  5 | ## Total Downloads Across All Channels
  6 | 
  7 | | Distribution Method | Total Downloads | Latest Version |
  8 | |---------------------|-----------------|----------------|
  9 | | **npm Package** | ![npm total](https://img.shields.io/npm/dt/@portel/ncp?label=total&color=blue) | ![npm monthly](https://img.shields.io/npm/dm/@portel/ncp?label=this%20month&color=blue) |
 10 | | **.mcpb Bundle** | ![GitHub total](https://img.shields.io/github/downloads/portel-dev/ncp/total?label=total&color=green) | ![GitHub latest](https://img.shields.io/github/downloads/portel-dev/ncp/latest/total?label=latest%20release&color=green) |
 11 | 
 12 | ---
 13 | 
 14 | ## Distribution Breakdown
 15 | 
 16 | ### npm Package
 17 | - **Best for:** All MCP clients (Cursor, Cline, Continue, VS Code)
 18 | - **Includes:** Full CLI tools + MCP server
 19 | - **Auto-updates:** Via `npm update -g @portel/ncp`
 20 | - **Package size:** ~2.5MB
 21 | 
 22 | [![npm version](https://img.shields.io/npm/v/@portel/ncp.svg?style=for-the-badge)](https://www.npmjs.com/package/@portel/ncp)
 23 | [![npm downloads](https://img.shields.io/npm/dm/@portel/ncp.svg?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@portel/ncp)
 24 | 
 25 | ### .mcpb Bundle
 26 | - **Best for:** Claude Desktop users
 27 | - **Includes:** Slim MCP-only server (no CLI)
 28 | - **Auto-updates:** Through Claude Desktop
 29 | - **Bundle size:** 126KB (13% smaller)
 30 | 
 31 | [![GitHub release](https://img.shields.io/github/v/release/portel-dev/ncp?style=for-the-badge)](https://github.com/portel-dev/ncp/releases/latest)
 32 | [![.mcpb downloads](https://img.shields.io/github/downloads/portel-dev/ncp/total?style=for-the-badge&label=downloads&color=green)](https://github.com/portel-dev/ncp/releases)
 33 | 
 34 | ---
 35 | 
 36 | ## Growth Metrics
 37 | 
 38 | ### Monthly npm Downloads
 39 | ![npm trends](https://img.shields.io/npm/dm/@portel/ncp?style=for-the-badge&label=NPM%20Downloads%20This%20Month)
 40 | 
 41 | ### GitHub Release Downloads
 42 | ![GitHub downloads](https://img.shields.io/github/downloads/portel-dev/ncp/latest/total?style=for-the-badge&label=Latest%20Release%20Downloads)
 43 | 
 44 | ---
 45 | 
 46 | ## Version Adoption
 47 | 
 48 | | Version | Release Date | Downloads | Status |
 49 | |---------|--------------|-----------|--------|
 50 | | Latest | ![GitHub release date](https://img.shields.io/github/release-date/portel-dev/ncp) | ![Downloads](https://img.shields.io/github/downloads/portel-dev/ncp/latest/total) | ✅ Active |
 51 | 
 52 | ---
 53 | 
 54 | ## How to Check Live Stats
 55 | 
 56 | ### Via npm
 57 | ```bash
 58 | npm info @portel/ncp
 59 | # Or use npm-stat for detailed analytics
 60 | npx npm-stat @portel/ncp
 61 | ```
 62 | 
 63 | ### Via GitHub API
 64 | ```bash
 65 | # Run our custom script
 66 | node scripts/check-mcpb-downloads.js
 67 | ```
 68 | 
 69 | ### Via Shields.io API
 70 | ```bash
 71 | # npm downloads (all time)
 72 | curl https://img.shields.io/npm/dt/@portel/ncp.json
 73 | 
 74 | # GitHub release downloads (all time)
 75 | curl https://img.shields.io/github/downloads/portel-dev/ncp/total.json
 76 | ```
 77 | 
 78 | ---
 79 | 
 80 | ## Platform Breakdown
 81 | 
 82 | *Note: Platform-specific download statistics require implementing telemetry (see `docs/guides/telemetry-design.md`)*
 83 | 
 84 | Without telemetry, we can only track:
 85 | - ✅ Total downloads (npm + GitHub)
 86 | - ✅ Downloads per version
 87 | - ❌ Platform distribution (macOS/Windows/Linux)
 88 | - ❌ Active installations
 89 | - ❌ MCP client distribution
 90 | 
 91 | ---
 92 | 
 93 | ## Credibility Indicators
 94 | 
 95 | Use these badges in your documentation, website, or presentations:
 96 | 
 97 | ### Compact Badges (for README)
 98 | ```markdown
 99 | [![npm downloads](https://img.shields.io/npm/dm/@portel/ncp.svg)](https://npmjs.com/package/@portel/ncp)
100 | [![.mcpb downloads](https://img.shields.io/github/downloads/portel-dev/ncp/total)](https://github.com/portel-dev/ncp/releases)
101 | ```
102 | 
103 | ### Large Badges (for landing pages)
104 | ```markdown
105 | [![npm downloads](https://img.shields.io/npm/dm/@portel/ncp?style=for-the-badge&color=blue)](https://npmjs.com/package/@portel/ncp)
106 | [![.mcpb downloads](https://img.shields.io/github/downloads/portel-dev/ncp/total?style=for-the-badge&color=green)](https://github.com/portel-dev/ncp/releases)
107 | ```
108 | 
109 | ---
110 | 
111 | ## Share Your Success
112 | 
113 | Once you hit download milestones, celebrate with the community:
114 | 
115 | - 🎉 **1,000 downloads** - "NCP has reached 1K downloads! Thank you!"
116 | - 🚀 **10,000 downloads** - "10K downloads milestone! Here's what's next..."
117 | - 🌟 **100,000 downloads** - "100K downloads! Here's the impact..."
118 | 
119 | Share on:
120 | - Twitter/X with `#ModelContextProtocol` `#MCP`
121 | - LinkedIn tech community
122 | - Hacker News (Show HN)
123 | - Reddit (r/ChatGPT, r/ClaudeAI, r/LocalLLaMA)
124 | 
```

--------------------------------------------------------------------------------
/src/cache/schema-cache.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Schema Cache
  3 |  *
  4 |  * Caches MCP configuration schemas for reuse across add/repair/import commands
  5 |  * Stores in JSON files alongside CSV cache
  6 |  */
  7 | 
  8 | import fs from 'fs';
  9 | import path from 'path';
 10 | import { ConfigurationSchema } from '../services/config-schema-reader.js';
 11 | import { logger } from '../utils/logger.js';
 12 | 
 13 | export class SchemaCache {
 14 |   private cacheDir: string;
 15 | 
 16 |   constructor(cacheDir: string) {
 17 |     this.cacheDir = path.join(cacheDir, 'schemas');
 18 |     this.ensureCacheDir();
 19 |   }
 20 | 
 21 |   /**
 22 |    * Ensure cache directory exists
 23 |    */
 24 |   private ensureCacheDir(): void {
 25 |     if (!fs.existsSync(this.cacheDir)) {
 26 |       fs.mkdirSync(this.cacheDir, { recursive: true });
 27 |     }
 28 |   }
 29 | 
 30 |   /**
 31 |    * Save configuration schema to cache
 32 |    */
 33 |   save(mcpName: string, schema: ConfigurationSchema): void {
 34 |     try {
 35 |       const filepath = this.getSchemaPath(mcpName);
 36 |       const data = {
 37 |         mcpName,
 38 |         schema,
 39 |         cachedAt: new Date().toISOString(),
 40 |         version: '1.0'
 41 |       };
 42 | 
 43 |       fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf-8');
 44 |       logger.debug(`Cached configuration schema for ${mcpName}`);
 45 |     } catch (error: any) {
 46 |       logger.error(`Failed to cache schema for ${mcpName}:`, error.message);
 47 |     }
 48 |   }
 49 | 
 50 |   /**
 51 |    * Load configuration schema from cache
 52 |    */
 53 |   get(mcpName: string): ConfigurationSchema | null {
 54 |     try {
 55 |       const filepath = this.getSchemaPath(mcpName);
 56 | 
 57 |       if (!fs.existsSync(filepath)) {
 58 |         return null;
 59 |       }
 60 | 
 61 |       const data = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
 62 |       logger.debug(`Loaded cached schema for ${mcpName}`);
 63 |       return data.schema as ConfigurationSchema;
 64 |     } catch (error: any) {
 65 |       logger.error(`Failed to load cached schema for ${mcpName}:`, error.message);
 66 |       return null;
 67 |     }
 68 |   }
 69 | 
 70 |   /**
 71 |    * Check if schema is cached
 72 |    */
 73 |   has(mcpName: string): boolean {
 74 |     const filepath = this.getSchemaPath(mcpName);
 75 |     return fs.existsSync(filepath);
 76 |   }
 77 | 
 78 |   /**
 79 |    * Delete cached schema
 80 |    */
 81 |   delete(mcpName: string): void {
 82 |     try {
 83 |       const filepath = this.getSchemaPath(mcpName);
 84 | 
 85 |       if (fs.existsSync(filepath)) {
 86 |         fs.unlinkSync(filepath);
 87 |         logger.debug(`Deleted cached schema for ${mcpName}`);
 88 |       }
 89 |     } catch (error: any) {
 90 |       logger.error(`Failed to delete cached schema for ${mcpName}:`, error.message);
 91 |     }
 92 |   }
 93 | 
 94 |   /**
 95 |    * Clear all cached schemas
 96 |    */
 97 |   clear(): void {
 98 |     try {
 99 |       if (fs.existsSync(this.cacheDir)) {
100 |         const files = fs.readdirSync(this.cacheDir);
101 |         for (const file of files) {
102 |           if (file.endsWith('.schema.json')) {
103 |             fs.unlinkSync(path.join(this.cacheDir, file));
104 |           }
105 |         }
106 |         logger.debug('Cleared all cached schemas');
107 |       }
108 |     } catch (error: any) {
109 |       logger.error('Failed to clear schema cache:', error.message);
110 |     }
111 |   }
112 | 
113 |   /**
114 |    * Get all cached schemas
115 |    */
116 |   listAll(): Array<{ mcpName: string; cachedAt: string }> {
117 |     try {
118 |       if (!fs.existsSync(this.cacheDir)) {
119 |         return [];
120 |       }
121 | 
122 |       const files = fs.readdirSync(this.cacheDir);
123 |       const schemas: Array<{ mcpName: string; cachedAt: string }> = [];
124 | 
125 |       for (const file of files) {
126 |         if (file.endsWith('.schema.json')) {
127 |           const filepath = path.join(this.cacheDir, file);
128 |           const data = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
129 |           schemas.push({
130 |             mcpName: data.mcpName,
131 |             cachedAt: data.cachedAt
132 |           });
133 |         }
134 |       }
135 | 
136 |       return schemas;
137 |     } catch (error: any) {
138 |       logger.error('Failed to list cached schemas:', error.message);
139 |       return [];
140 |     }
141 |   }
142 | 
143 |   /**
144 |    * Get file path for schema
145 |    */
146 |   private getSchemaPath(mcpName: string): string {
147 |     // Sanitize MCP name for filename
148 |     const safeName = mcpName.replace(/[^a-zA-Z0-9-_]/g, '_');
149 |     return path.join(this.cacheDir, `${safeName}.schema.json`);
150 |   }
151 | 
152 |   /**
153 |    * Get cache statistics
154 |    */
155 |   getStats(): { total: number; oldestCache: string | null; newestCache: string | null } {
156 |     const all = this.listAll();
157 | 
158 |     if (all.length === 0) {
159 |       return { total: 0, oldestCache: null, newestCache: null };
160 |     }
161 | 
162 |     const sorted = all.sort((a, b) =>
163 |       new Date(a.cachedAt).getTime() - new Date(b.cachedAt).getTime()
164 |     );
165 | 
166 |     return {
167 |       total: all.length,
168 |       oldestCache: sorted[0].cachedAt,
169 |       newestCache: sorted[sorted.length - 1].cachedAt
170 |     };
171 |   }
172 | }
173 | 
```

--------------------------------------------------------------------------------
/src/utils/schema-examples.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Dynamic Schema Examples Generator
  3 |  * 
  4 |  * Generates realistic examples from actual available tools instead of hardcoded dummy examples
  5 |  */
  6 | 
  7 | export class SchemaExamplesGenerator {
  8 |   private tools: Array<{name: string, description: string}> = [];
  9 |   
 10 |   constructor(availableTools: Array<{name: string, description: string}>) {
 11 |     this.tools = availableTools;
 12 |   }
 13 |   
 14 |   /**
 15 |    * Get realistic tool execution examples
 16 |    */
 17 |   getToolExecutionExamples(): string[] {
 18 |     const examples: string[] = [];
 19 |     
 20 |     // Always include Shell if available (most common)
 21 |     const shellTool = this.tools.find(t => t.name.startsWith('Shell:'));
 22 |     if (shellTool) {
 23 |       examples.push(shellTool.name);
 24 |     }
 25 |     
 26 |     // Add file operation example
 27 |     const fileTools = this.tools.filter(t => 
 28 |       t.description?.toLowerCase().includes('file') || 
 29 |       t.name.includes('write') || 
 30 |       t.name.includes('read')
 31 |     );
 32 |     if (fileTools.length > 0) {
 33 |       examples.push(fileTools[0].name);
 34 |     }
 35 |     
 36 |     // Add one more diverse example
 37 |     const otherTool = this.tools.find(t => 
 38 |       !t.name.startsWith('Shell:') && 
 39 |       !examples.includes(t.name)
 40 |     );
 41 |     if (otherTool) {
 42 |       examples.push(otherTool.name);
 43 |     }
 44 |     
 45 |     return examples.slice(0, 3); // Max 3 examples
 46 |   }
 47 |   
 48 |   /**
 49 |    * Get realistic discovery query examples
 50 |    */
 51 |   getDiscoveryExamples(): string[] {
 52 |     const examples: string[] = [];
 53 |     
 54 |     // Analyze available tools to suggest realistic queries
 55 |     const hasFileOps = this.tools.some(t => t.description?.toLowerCase().includes('file'));
 56 |     const hasGit = this.tools.some(t => t.description?.toLowerCase().includes('git'));
 57 |     const hasWeb = this.tools.some(t => t.description?.toLowerCase().includes('web') || t.description?.toLowerCase().includes('search'));
 58 |     
 59 |     if (hasFileOps) examples.push('create a new file');
 60 |     if (hasGit) examples.push('check git status');
 61 |     if (hasWeb) examples.push('search the web');
 62 |     
 63 |     // Generic fallbacks
 64 |     if (examples.length === 0) {
 65 |       examples.push('run a command', 'list files');
 66 |     }
 67 |     
 68 |     return examples.slice(0, 3);
 69 |   }
 70 |   
 71 |   /**
 72 |    * Generate complete tool execution schema with dynamic examples
 73 |    */
 74 |   getToolExecutionSchema() {
 75 |     const examples = this.getToolExecutionExamples();
 76 |     const exampleText = examples.length > 0 
 77 |       ? `(e.g., ${examples.map(e => `"${e}"`).join(', ')})`
 78 |       : '(use the discover_tools command to find available tools)';
 79 |       
 80 |     return {
 81 |       type: 'string',
 82 |       description: `The specific tool name to execute ${exampleText}`
 83 |     };
 84 |   }
 85 |   
 86 |   /**
 87 |    * Generate discovery schema with realistic examples
 88 |    */
 89 |   getDiscoverySchema() {
 90 |     const examples = this.getDiscoveryExamples();
 91 |     const exampleText = examples.length > 0 
 92 |       ? `(e.g., ${examples.map(e => `"${e}"`).join(', ')})`
 93 |       : '';
 94 |       
 95 |     return {
 96 |       type: 'string',
 97 |       description: `Natural language description of what you want to do ${exampleText}`
 98 |     };
 99 |   }
100 |   
101 |   /**
102 |    * Get tool categories for better organization
103 |    */
104 |   getToolCategories(): {[category: string]: string[]} {
105 |     const categories: {[category: string]: string[]} = {};
106 |     
107 |     for (const tool of this.tools) {
108 |       const desc = tool.description?.toLowerCase() || '';
109 |       const name = tool.name.toLowerCase();
110 |       
111 |       if (name.includes('shell') || desc.includes('command')) {
112 |         categories['System Commands'] = categories['System Commands'] || [];
113 |         categories['System Commands'].push(tool.name);
114 |       } else if (desc.includes('file') || name.includes('read') || name.includes('write')) {
115 |         categories['File Operations'] = categories['File Operations'] || [];
116 |         categories['File Operations'].push(tool.name);
117 |       } else if (desc.includes('web') || desc.includes('search')) {
118 |         categories['Web & Search'] = categories['Web & Search'] || [];
119 |         categories['Web & Search'].push(tool.name);
120 |       } else if (desc.includes('git')) {
121 |         categories['Git Operations'] = categories['Git Operations'] || [];
122 |         categories['Git Operations'].push(tool.name);
123 |       } else {
124 |         categories['Other Tools'] = categories['Other Tools'] || [];
125 |         categories['Other Tools'].push(tool.name);
126 |       }
127 |     }
128 |     
129 |     return categories;
130 |   }
131 | }
132 | 
133 | /**
134 |  * Fallback examples when no tools are available yet (startup scenario)
135 |  */
136 | export const FALLBACK_EXAMPLES = {
137 |   toolExecution: [
138 |     '"Shell:run_command"',
139 |     '"desktop-commander:write_file"'
140 |   ],
141 |   discovery: [
142 |     '"run a shell command"',
143 |     '"create a new file"',
144 |     '"search for something"'
145 |   ]
146 | };
```

--------------------------------------------------------------------------------
/test/mock-mcps/aws-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock AWS MCP Server
  5 |  * Real MCP server structure for AWS services testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'aws-test',
 12 |   version: '1.0.0',
 13 |   description: 'Amazon Web Services integration for EC2, S3, Lambda, and cloud resource management'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'create_ec2_instance',
 19 |     description: 'Launch new EC2 virtual machine instances with configuration. Create servers, deploy applications to cloud.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         image_id: {
 24 |           type: 'string',
 25 |           description: 'AMI ID for instance'
 26 |         },
 27 |         instance_type: {
 28 |           type: 'string',
 29 |           description: 'Instance size (t2.micro, m5.large, etc.)'
 30 |         },
 31 |         key_name: {
 32 |           type: 'string',
 33 |           description: 'Key pair name for SSH access'
 34 |         },
 35 |         security_groups: {
 36 |           type: 'array',
 37 |           description: 'Security group names',
 38 |           items: { type: 'string' }
 39 |         },
 40 |         tags: {
 41 |           type: 'object',
 42 |           description: 'Instance tags as key-value pairs'
 43 |         }
 44 |       },
 45 |       required: ['image_id', 'instance_type']
 46 |     }
 47 |   },
 48 |   {
 49 |     name: 'upload_to_s3',
 50 |     description: 'Upload files and objects to S3 storage buckets. Store files in cloud, backup data, host static content.',
 51 |     inputSchema: {
 52 |       type: 'object',
 53 |       properties: {
 54 |         bucket: {
 55 |           type: 'string',
 56 |           description: 'S3 bucket name'
 57 |         },
 58 |         key: {
 59 |           type: 'string',
 60 |           description: 'Object key/path in bucket'
 61 |         },
 62 |         file_path: {
 63 |           type: 'string',
 64 |           description: 'Local file path to upload'
 65 |         },
 66 |         content_type: {
 67 |           type: 'string',
 68 |           description: 'MIME type of file'
 69 |         },
 70 |         public: {
 71 |           type: 'boolean',
 72 |           description: 'Make object publicly accessible'
 73 |         }
 74 |       },
 75 |       required: ['bucket', 'key', 'file_path']
 76 |     }
 77 |   },
 78 |   {
 79 |     name: 'create_lambda_function',
 80 |     description: 'Deploy serverless Lambda functions for event-driven computing. Run code without servers, process events.',
 81 |     inputSchema: {
 82 |       type: 'object',
 83 |       properties: {
 84 |         function_name: {
 85 |           type: 'string',
 86 |           description: 'Lambda function name'
 87 |         },
 88 |         runtime: {
 89 |           type: 'string',
 90 |           description: 'Runtime environment (nodejs18.x, python3.9, etc.)'
 91 |         },
 92 |         handler: {
 93 |           type: 'string',
 94 |           description: 'Function handler entry point'
 95 |         },
 96 |         code: {
 97 |           type: 'object',
 98 |           description: 'Function code (zip file or inline)'
 99 |         },
100 |         role: {
101 |           type: 'string',
102 |           description: 'IAM role ARN for execution'
103 |         }
104 |       },
105 |       required: ['function_name', 'runtime', 'handler', 'code', 'role']
106 |     }
107 |   },
108 |   {
109 |     name: 'list_resources',
110 |     description: 'List AWS resources across services with filtering options. View EC2 instances, S3 buckets, Lambda functions.',
111 |     inputSchema: {
112 |       type: 'object',
113 |       properties: {
114 |         service: {
115 |           type: 'string',
116 |           description: 'AWS service name (ec2, s3, lambda, etc.)'
117 |         },
118 |         region: {
119 |           type: 'string',
120 |           description: 'AWS region to query'
121 |         },
122 |         filters: {
123 |           type: 'object',
124 |           description: 'Service-specific filters'
125 |         }
126 |       },
127 |       required: ['service']
128 |     }
129 |   },
130 |   {
131 |     name: 'create_rds_database',
132 |     description: 'Create managed RDS database instances with configuration. Set up MySQL, PostgreSQL databases in cloud.',
133 |     inputSchema: {
134 |       type: 'object',
135 |       properties: {
136 |         db_name: {
137 |           type: 'string',
138 |           description: 'Database instance identifier'
139 |         },
140 |         engine: {
141 |           type: 'string',
142 |           description: 'Database engine (mysql, postgres, etc.)'
143 |         },
144 |         instance_class: {
145 |           type: 'string',
146 |           description: 'Database instance size'
147 |         },
148 |         allocated_storage: {
149 |           type: 'number',
150 |           description: 'Storage size in GB'
151 |         },
152 |         username: {
153 |           type: 'string',
154 |           description: 'Master username'
155 |         },
156 |         password: {
157 |           type: 'string',
158 |           description: 'Master password'
159 |         }
160 |       },
161 |       required: ['db_name', 'engine', 'instance_class', 'allocated_storage', 'username', 'password']
162 |     }
163 |   }
164 | ];
165 | 
166 | // Create and run the server
167 | const server = new MockMCPServer(serverInfo, tools);
168 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/docs/mcp-registry-setup.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Registry Publishing Setup
  2 | 
  3 | ## Overview
  4 | 
  5 | NCP automatically publishes to the MCP Registry when a GitHub Release is created. This document explains the authentication setup.
  6 | 
  7 | ## Authentication Methods
  8 | 
  9 | ### Method 1: GitHub OIDC (Automatic)
 10 | 
 11 | **Pros**:
 12 | - ✅ No secrets to configure
 13 | - ✅ Automatic via GitHub Actions
 14 | - ✅ Most secure (short-lived tokens)
 15 | 
 16 | **Cons**:
 17 | - ⚠️ May not detect organization membership correctly
 18 | - ⚠️ Known issue with `portel-dev` organization detection
 19 | 
 20 | **How it works**:
 21 | - Workflow has `id-token: write` permission
 22 | - GitHub Actions generates OIDC token automatically
 23 | - MCP Publisher uses token to authenticate
 24 | 
 25 | **No setup required** - works out of the box (if organization detection works)
 26 | 
 27 | ---
 28 | 
 29 | ### Method 2: GitHub Personal Access Token (Fallback)
 30 | 
 31 | **Use this if OIDC fails to detect `portel-dev` organization**
 32 | 
 33 | #### Setup Steps
 34 | 
 35 | 1. **Create GitHub PAT**:
 36 |    - Go to: https://github.com/settings/tokens
 37 |    - Click: "Tokens (classic)" → "Generate new token (classic)"
 38 |    - Name: `MCP Registry Publishing`
 39 |    - Scopes needed:
 40 |      - ✅ `repo` (Full control of private repositories)
 41 |      - ✅ `read:org` (Read org and team membership)
 42 |    - Click "Generate token"
 43 |    - **Copy the token** (you won't see it again!)
 44 | 
 45 | 2. **Add as GitHub Secret**:
 46 |    - Go to: https://github.com/portel-dev/ncp/settings/secrets/actions
 47 |    - Click: "New repository secret"
 48 |    - Name: `MCP_GITHUB_TOKEN`
 49 |    - Value: Paste your PAT
 50 |    - Click: "Add secret"
 51 | 
 52 | 3. **Workflow will automatically use it**:
 53 |    - Workflow tries OIDC first
 54 |    - If OIDC fails, falls back to `MCP_GITHUB_TOKEN`
 55 |    - No code changes needed!
 56 | 
 57 | ---
 58 | 
 59 | ## Verification
 60 | 
 61 | ### Check Organization Membership
 62 | 
 63 | Verify you're an admin of `portel-dev`:
 64 | 
 65 | ```bash
 66 | gh api orgs/portel-dev/memberships/$(gh api user -q .login)
 67 | ```
 68 | 
 69 | Expected output:
 70 | ```json
 71 | {
 72 |   "role": "admin",
 73 |   "state": "active"
 74 | }
 75 | ```
 76 | 
 77 | ### Check Repository Permissions
 78 | 
 79 | ```bash
 80 | gh api repos/portel-dev/ncp/collaborators/$(gh api user -q .login)/permission
 81 | ```
 82 | 
 83 | Expected output:
 84 | ```json
 85 | {
 86 |   "permission": "admin",
 87 |   "role_name": "admin"
 88 | }
 89 | ```
 90 | 
 91 | ### Validate server.json
 92 | 
 93 | ```bash
 94 | curl -sS https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json -o /tmp/server.schema.json
 95 | jsonschema -i server.json /tmp/server.schema.json
 96 | ```
 97 | 
 98 | Should output: (no errors = valid)
 99 | 
100 | ---
101 | 
102 | ## Testing the Workflow
103 | 
104 | ### Option 1: Wait for Real Release
105 | 
106 | The workflow triggers automatically when you publish a GitHub Release via the Release workflow.
107 | 
108 | ### Option 2: Manual Test (Local)
109 | 
110 | You can test MCP Publisher authentication locally:
111 | 
112 | ```bash
113 | # Download MCP Publisher
114 | VERSION="v1.1.0"
115 | OS=$(uname -s | tr '[:upper:]' '[:lower:]')
116 | ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
117 | 
118 | curl -L "https://github.com/modelcontextprotocol/registry/releases/download/${VERSION}/mcp-publisher_${VERSION#v}_${OS}_${ARCH}.tar.gz" | tar xz
119 | 
120 | # Test authentication
121 | ./mcp-publisher login github-oidc  # Try OIDC first
122 | 
123 | # Or with PAT
124 | export GITHUB_TOKEN="your-pat-here"
125 | echo "$GITHUB_TOKEN" | ./mcp-publisher login github --token-stdin
126 | 
127 | # Dry run publish (doesn't actually publish)
128 | ./mcp-publisher publish --dry-run
129 | ```
130 | 
131 | ---
132 | 
133 | ## Troubleshooting
134 | 
135 | ### "Organization portel-dev not detected"
136 | 
137 | **Solution**: Use GitHub PAT (Method 2 above)
138 | 
139 | This is a known limitation with GitHub OIDC tokens not always exposing organization membership.
140 | 
141 | ### "Authentication failed"
142 | 
143 | **Check**:
144 | 1. PAT is valid and not expired
145 | 2. PAT has `repo` and `read:org` scopes
146 | 3. Secret name is exactly `MCP_GITHUB_TOKEN`
147 | 4. You're an admin of `portel-dev` organization
148 | 
149 | ### "Invalid server.json"
150 | 
151 | **Check**:
152 | - Description is ≤100 characters
153 | - Version format is valid (e.g., `1.4.0`)
154 | - All required fields present
155 | - Run validation: `jsonschema -i server.json /tmp/server.schema.json`
156 | 
157 | ---
158 | 
159 | ## Security Notes
160 | 
161 | ### GitHub PAT Best Practices
162 | 
163 | - ✅ Use classic tokens (fine-grained tokens not yet supported by MCP Publisher)
164 | - ✅ Minimum scopes: `repo`, `read:org`
165 | - ✅ Store as GitHub Secret (never commit to code)
166 | - ✅ Rotate token periodically
167 | - ✅ Revoke immediately if compromised
168 | 
169 | ### OIDC vs PAT
170 | 
171 | | Feature | OIDC | PAT |
172 | |---------|------|-----|
173 | | Security | ⭐⭐⭐⭐⭐ Short-lived | ⭐⭐⭐ Long-lived |
174 | | Setup | Zero config | Requires secret |
175 | | Org Detection | ⚠️ May fail | ✅ Reliable |
176 | | Recommended | If it works | If OIDC fails |
177 | 
178 | ---
179 | 
180 | ## What Gets Published
181 | 
182 | Each release publishes to:
183 | 
184 | 1. **NPM**: `@portel/[email protected]`
185 | 2. **MCP Registry**: `io.github.portel-dev/ncp`
186 | 3. **GitHub Releases**: Tagged release
187 | 
188 | All automatic via GitHub Actions!
189 | 
190 | ---
191 | 
192 | ## Support
193 | 
194 | If you encounter issues:
195 | 
196 | 1. Check GitHub Actions logs
197 | 2. Review this troubleshooting guide
198 | 3. Test authentication locally
199 | 4. Open an issue: https://github.com/portel-dev/ncp/issues
200 | 
```

--------------------------------------------------------------------------------
/src/utils/parameter-prompter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Interactive parameter prompting system
  3 |  * Guides users through tool parameters with intelligent prompts
  4 |  */
  5 | import * as readline from 'readline';
  6 | import chalk from 'chalk';
  7 | 
  8 | export interface ParameterInfo {
  9 |   name: string;
 10 |   type: string;
 11 |   required: boolean;
 12 |   description?: string;
 13 | }
 14 | 
 15 | export class ParameterPrompter {
 16 |   private rl: readline.Interface;
 17 | 
 18 |   constructor() {
 19 |     this.rl = readline.createInterface({
 20 |       input: process.stdin,
 21 |       output: process.stdout
 22 |     });
 23 |   }
 24 | 
 25 |   /**
 26 |    * Prompt user for all tool parameters interactively
 27 |    */
 28 |   async promptForParameters(
 29 |     toolName: string,
 30 |     parameters: ParameterInfo[],
 31 |     predictor: any,
 32 |     toolContext: string
 33 |   ): Promise<any> {
 34 |     console.log(chalk.blue(`📝 Tool "${toolName}" requires parameters. Let me guide you through them:\n`));
 35 | 
 36 |     const result: any = {};
 37 | 
 38 |     // Sort parameters: required first, then optional
 39 |     const sortedParams = [...parameters].sort((a, b) => {
 40 |       if (a.required && !b.required) return -1;
 41 |       if (!a.required && b.required) return 1;
 42 |       return 0;
 43 |     });
 44 | 
 45 |     for (const param of sortedParams) {
 46 |       const value = await this.promptForParameter(param, predictor, toolContext, toolName);
 47 |       if (value !== null && value !== undefined && value !== '') {
 48 |         result[param.name] = this.convertValue(value, param.type);
 49 |       }
 50 |     }
 51 | 
 52 |     return result;
 53 |   }
 54 | 
 55 |   /**
 56 |    * Prompt for a single parameter
 57 |    */
 58 |   private async promptForParameter(
 59 |     param: ParameterInfo,
 60 |     predictor: any,
 61 |     toolContext: string,
 62 |     toolName: string
 63 |   ): Promise<string | null> {
 64 |     const icon = param.required ? '📄' : '📔';
 65 |     const status = param.required ? 'Required' : 'Optional';
 66 |     const typeInfo = chalk.cyan(`(${param.type})`);
 67 | 
 68 |     console.log(`${icon} ${chalk.bold(param.name)} ${typeInfo} - ${chalk.yellow(status)}`);
 69 | 
 70 |     if (param.description) {
 71 |       console.log(`   ${chalk.gray(param.description)}`);
 72 |     }
 73 | 
 74 |     // Generate intelligent suggestion
 75 |     const suggestion = predictor.predictValue(
 76 |       param.name,
 77 |       param.type,
 78 |       toolContext,
 79 |       param.description,
 80 |       toolName
 81 |     );
 82 | 
 83 |     let prompt = '   Enter value';
 84 |     if (!param.required) {
 85 |       prompt += ' (press Enter to skip)';
 86 |     }
 87 |     if (suggestion && typeof suggestion === 'string' && suggestion !== 'example') {
 88 |       prompt += ` [${chalk.green(suggestion)}]`;
 89 |     }
 90 |     prompt += ': ';
 91 | 
 92 |     const input = await this.question(prompt);
 93 | 
 94 |     // If user pressed Enter and we have a suggestion, use it
 95 |     if (input === '' && suggestion && param.required) {
 96 |       console.log(`   ${chalk.gray(`Using suggested value: ${suggestion}`)}`);
 97 |       return String(suggestion);
 98 |     }
 99 | 
100 |     // If optional and empty, skip
101 |     if (input === '' && !param.required) {
102 |       console.log(`   ${chalk.gray('Skipped')}`);
103 |       return null;
104 |     }
105 | 
106 |     // If required but empty, use suggestion or ask again
107 |     if (input === '' && param.required) {
108 |       if (suggestion) {
109 |         console.log(`   ${chalk.gray(`Using suggested value: ${suggestion}`)}`);
110 |         return String(suggestion);
111 |       } else {
112 |         console.log(chalk.red('   This parameter is required. Please provide a value.'));
113 |         return await this.promptForParameter(param, predictor, toolContext, toolName);
114 |       }
115 |     }
116 | 
117 |     console.log(); // Add spacing
118 |     return input;
119 |   }
120 | 
121 |   /**
122 |    * Convert string input to appropriate type
123 |    */
124 |   private convertValue(value: string, type: string): any {
125 |     if (value === '') return undefined;
126 | 
127 |     switch (type) {
128 |       case 'number':
129 |       case 'integer':
130 |         const num = Number(value);
131 |         return isNaN(num) ? value : num;
132 | 
133 |       case 'boolean':
134 |         const lower = value.toLowerCase();
135 |         if (lower === 'true' || lower === 'yes' || lower === '1') return true;
136 |         if (lower === 'false' || lower === 'no' || lower === '0') return false;
137 |         return Boolean(value);
138 | 
139 |       case 'array':
140 |         try {
141 |           // Try to parse as JSON array first
142 |           if (value.startsWith('[')) {
143 |             return JSON.parse(value);
144 |           }
145 |           // Otherwise split by comma
146 |           return value.split(',').map(s => s.trim());
147 |         } catch {
148 |           return value.split(',').map(s => s.trim());
149 |         }
150 | 
151 |       case 'object':
152 |         try {
153 |           return JSON.parse(value);
154 |         } catch {
155 |           return { value };
156 |         }
157 | 
158 |       default:
159 |         return value;
160 |     }
161 |   }
162 | 
163 |   /**
164 |    * Prompt user with a question
165 |    */
166 |   private question(prompt: string): Promise<string> {
167 |     return new Promise((resolve) => {
168 |       this.rl.question(prompt, (answer) => {
169 |         resolve(answer.trim());
170 |       });
171 |     });
172 |   }
173 | 
174 |   /**
175 |    * Close the readline interface
176 |    */
177 |   close(): void {
178 |     this.rl.close();
179 |   }
180 | }
```

--------------------------------------------------------------------------------
/src/utils/paths.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * NCP Standardized File System Paths
  3 |  *
  4 |  * This file defines all file paths used by NCP components.
  5 |  * DO NOT hardcode paths elsewhere - always import from here.
  6 |  *
  7 |  * Cross-platform directory locations:
  8 |  * - Windows: %APPDATA%\ncp\ (e.g., C:\Users\Username\AppData\Roaming\ncp\)
  9 |  * - macOS: ~/Library/Preferences/ncp/
 10 |  * - Linux: ~/.config/ncp/
 11 |  *
 12 |  * See NCP_FILE_SYSTEM_ARCHITECTURE.md for complete documentation.
 13 |  */
 14 | 
 15 | import * as path from 'path';
 16 | import * as os from 'os';
 17 | import * as fs from 'fs/promises';
 18 | import envPaths from 'env-paths';
 19 | 
 20 | // Cross-platform user directories using env-paths
 21 | const paths = envPaths('ncp');
 22 | 
 23 | // Base Directories (cross-platform)
 24 | export const NCP_BASE_DIR = paths.config;
 25 | export const NCP_PROFILES_DIR = path.join(NCP_BASE_DIR, 'profiles');
 26 | export const NCP_CACHE_DIR = path.join(NCP_BASE_DIR, 'cache');
 27 | export const NCP_LOGS_DIR = path.join(NCP_BASE_DIR, 'logs');
 28 | export const NCP_CONFIG_DIR = path.join(NCP_BASE_DIR, 'config');
 29 | export const NCP_TEMP_DIR = path.join(NCP_BASE_DIR, 'temp');
 30 | 
 31 | // Profile Files
 32 | export const PROFILE_ALL = path.join(NCP_PROFILES_DIR, 'all.json');
 33 | export const PROFILE_CLAUDE_DESKTOP = path.join(NCP_PROFILES_DIR, 'claude-desktop.json');
 34 | export const PROFILE_CLAUDE_CODE = path.join(NCP_PROFILES_DIR, 'claude-code.json');
 35 | export const PROFILE_DEV = path.join(NCP_PROFILES_DIR, 'dev.json');
 36 | export const PROFILE_MINIMAL = path.join(NCP_PROFILES_DIR, 'minimal.json');
 37 | 
 38 | // Cache Files (currently stored in base directory, not cache subdirectory)
 39 | export const TOOL_CACHE_FILE = path.join(NCP_BASE_DIR, 'tool-cache.json');
 40 | export const MCP_HEALTH_CACHE = path.join(NCP_BASE_DIR, 'mcp-health.json');
 41 | export const DISCOVERY_INDEX_CACHE = path.join(NCP_CACHE_DIR, 'discovery-index.json');
 42 | 
 43 | // Profile-specific vector database files
 44 | export const EMBEDDINGS_DIR = path.join(NCP_CACHE_DIR, 'embeddings');
 45 | export const EMBEDDINGS_METADATA_DIR = path.join(NCP_CACHE_DIR, 'metadata');
 46 | 
 47 | // Log Files
 48 | export const MAIN_LOG_FILE = path.join(NCP_LOGS_DIR, 'ncp.log');
 49 | export const MCP_LOG_FILE = path.join(NCP_LOGS_DIR, 'mcp-connections.log');
 50 | export const DISCOVERY_LOG_FILE = path.join(NCP_LOGS_DIR, 'discovery.log');
 51 | 
 52 | // Config Files
 53 | export const GLOBAL_SETTINGS = path.join(NCP_CONFIG_DIR, 'settings.json');
 54 | 
 55 | // Client-specific configs
 56 | export const CLIENT_CONFIGS_DIR = path.join(NCP_CONFIG_DIR, 'client-configs');
 57 | export const CLAUDE_DESKTOP_CONFIG = path.join(CLIENT_CONFIGS_DIR, 'claude-desktop.json');
 58 | export const CLAUDE_CODE_CONFIG = path.join(CLIENT_CONFIGS_DIR, 'claude-code.json');
 59 | 
 60 | // Temporary directories
 61 | export const MCP_PROBES_TEMP = path.join(NCP_TEMP_DIR, 'mcp-probes');
 62 | export const INSTALLATION_TEMP = path.join(NCP_TEMP_DIR, 'installation');
 63 | 
 64 | /**
 65 |  * Ensures all NCP directories exist
 66 |  * MUST be called on startup by all NCP components
 67 |  */
 68 | export async function ensureNCPDirectories(): Promise<void> {
 69 |   const directories = [
 70 |     NCP_BASE_DIR,
 71 |     NCP_PROFILES_DIR,
 72 |     NCP_CACHE_DIR,
 73 |     NCP_LOGS_DIR,
 74 |     NCP_CONFIG_DIR,
 75 |     NCP_TEMP_DIR,
 76 |     CLIENT_CONFIGS_DIR,
 77 |     MCP_PROBES_TEMP,
 78 |     INSTALLATION_TEMP,
 79 |     EMBEDDINGS_DIR,
 80 |     EMBEDDINGS_METADATA_DIR
 81 |   ];
 82 | 
 83 |   for (const dir of directories) {
 84 |     try {
 85 |       await fs.mkdir(dir, { recursive: true });
 86 |     } catch (error) {
 87 |       console.error(`Failed to create directory ${dir}:`, error);
 88 |     }
 89 |   }
 90 | }
 91 | 
 92 | /**
 93 |  * Gets profile path by name
 94 |  * @param profileName - Name of the profile (without .json extension)
 95 |  * @returns Full path to profile file
 96 |  */
 97 | export function getProfilePath(profileName: string): string {
 98 |   return path.join(NCP_PROFILES_DIR, `${profileName}.json`);
 99 | }
100 | 
101 | /**
102 |  * Migration utility: Move file if it exists at old location
103 |  * @param oldPath - Current file location
104 |  * @param newPath - New standardized location
105 |  */
106 | export async function migrateFile(oldPath: string, newPath: string): Promise<boolean> {
107 |   try {
108 |     // Check if old file exists
109 |     await fs.access(oldPath);
110 |     
111 |     // Ensure new directory exists
112 |     await fs.mkdir(path.dirname(newPath), { recursive: true });
113 |     
114 |     // Move file
115 |     await fs.rename(oldPath, newPath);
116 |     console.log(`Migrated: ${oldPath} → ${newPath}`);
117 |     return true;
118 |   } catch (error) {
119 |     // File doesn't exist at old location, that's fine
120 |     return false;
121 |   }
122 | }
123 | 
124 | /**
125 |  * Migrate all files from old locations to standardized locations
126 |  * Should be called once during upgrade
127 |  */
128 | export async function migrateAllFiles(): Promise<void> {
129 |   console.log('Starting NCP file migration...');
130 |   
131 |   // Migrate tool cache
132 |   await migrateFile('.tool-cache.json', TOOL_CACHE_FILE);
133 |   await migrateFile('tool-cache.json', TOOL_CACHE_FILE);
134 |   
135 |   // Migrate old profile files
136 |   await migrateFile('.ncp/profiles/default.json', PROFILE_ALL);
137 |   await migrateFile('.ncp/profiles/development.json', PROFILE_DEV);
138 |   
139 |   console.log('NCP file migration complete');
140 | }
```

--------------------------------------------------------------------------------
/test/mock-mcps/filesystem-server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Mock Filesystem MCP Server
  5 |  * Real MCP server structure for file system operations testing
  6 |  */
  7 | 
  8 | import { MockMCPServer } from './base-mock-server.js';
  9 | 
 10 | const serverInfo = {
 11 |   name: 'filesystem-test',
 12 |   version: '1.0.0',
 13 |   description: 'Local file system operations including reading, writing, directory management, and permissions'
 14 | };
 15 | 
 16 | const tools = [
 17 |   {
 18 |     name: 'read_file',
 19 |     description: 'Read contents of files from local filesystem. Load configuration files, read text documents, access data files.',
 20 |     inputSchema: {
 21 |       type: 'object',
 22 |       properties: {
 23 |         path: {
 24 |           type: 'string',
 25 |           description: 'File path to read'
 26 |         },
 27 |         encoding: {
 28 |           type: 'string',
 29 |           description: 'Text encoding (utf8, ascii, etc.)'
 30 |         }
 31 |       },
 32 |       required: ['path']
 33 |     }
 34 |   },
 35 |   {
 36 |     name: 'write_file',
 37 |     description: 'Write content to files on local filesystem. Create configuration files, save data, generate reports.',
 38 |     inputSchema: {
 39 |       type: 'object',
 40 |       properties: {
 41 |         path: {
 42 |           type: 'string',
 43 |           description: 'File path to write to'
 44 |         },
 45 |         content: {
 46 |           type: 'string',
 47 |           description: 'Content to write to file'
 48 |         },
 49 |         encoding: {
 50 |           type: 'string',
 51 |           description: 'Text encoding (utf8, ascii, etc.)'
 52 |         },
 53 |         create_dirs: {
 54 |           type: 'boolean',
 55 |           description: 'Create parent directories if they do not exist'
 56 |         }
 57 |       },
 58 |       required: ['path', 'content']
 59 |     }
 60 |   },
 61 |   {
 62 |     name: 'create_directory',
 63 |     description: 'Create new directories and folder structures. Organize files, set up project structure, create folder hierarchies.',
 64 |     inputSchema: {
 65 |       type: 'object',
 66 |       properties: {
 67 |         path: {
 68 |           type: 'string',
 69 |           description: 'Directory path to create'
 70 |         },
 71 |         recursive: {
 72 |           type: 'boolean',
 73 |           description: 'Create parent directories if needed'
 74 |         },
 75 |         mode: {
 76 |           type: 'string',
 77 |           description: 'Directory permissions (octal notation)'
 78 |         }
 79 |       },
 80 |       required: ['path']
 81 |     }
 82 |   },
 83 |   {
 84 |     name: 'list_directory',
 85 |     description: 'List files and directories with filtering and sorting options. Browse folders, find files, explore directory structure.',
 86 |     inputSchema: {
 87 |       type: 'object',
 88 |       properties: {
 89 |         path: {
 90 |           type: 'string',
 91 |           description: 'Directory path to list'
 92 |         },
 93 |         recursive: {
 94 |           type: 'boolean',
 95 |           description: 'Include subdirectories recursively'
 96 |         },
 97 |         include_hidden: {
 98 |           type: 'boolean',
 99 |           description: 'Include hidden files and directories'
100 |         },
101 |         pattern: {
102 |           type: 'string',
103 |           description: 'Glob pattern to filter files'
104 |         }
105 |       },
106 |       required: ['path']
107 |     }
108 |   },
109 |   {
110 |     name: 'delete_file',
111 |     description: 'Delete files and directories from filesystem. Remove old files, clean up temporary data, delete folders.',
112 |     inputSchema: {
113 |       type: 'object',
114 |       properties: {
115 |         path: {
116 |           type: 'string',
117 |           description: 'File or directory path to delete'
118 |         },
119 |         recursive: {
120 |           type: 'boolean',
121 |           description: 'Delete directories and contents recursively'
122 |         },
123 |         force: {
124 |           type: 'boolean',
125 |           description: 'Force deletion without confirmation'
126 |         }
127 |       },
128 |       required: ['path']
129 |     }
130 |   },
131 |   {
132 |     name: 'copy_file',
133 |     description: 'Copy files and directories to new locations. Backup files, duplicate data, organize content.',
134 |     inputSchema: {
135 |       type: 'object',
136 |       properties: {
137 |         source: {
138 |           type: 'string',
139 |           description: 'Source file or directory path'
140 |         },
141 |         destination: {
142 |           type: 'string',
143 |           description: 'Destination path for copy'
144 |         },
145 |         overwrite: {
146 |           type: 'boolean',
147 |           description: 'Overwrite destination if it exists'
148 |         },
149 |         preserve_attributes: {
150 |           type: 'boolean',
151 |           description: 'Preserve file timestamps and permissions'
152 |         }
153 |       },
154 |       required: ['source', 'destination']
155 |     }
156 |   },
157 |   {
158 |     name: 'get_file_info',
159 |     description: 'Get detailed information about files and directories. Check file size, modification time, permissions.',
160 |     inputSchema: {
161 |       type: 'object',
162 |       properties: {
163 |         path: {
164 |           type: 'string',
165 |           description: 'File or directory path'
166 |         },
167 |         follow_symlinks: {
168 |           type: 'boolean',
169 |           description: 'Follow symbolic links'
170 |         }
171 |       },
172 |       required: ['path']
173 |     }
174 |   }
175 | ];
176 | 
177 | // Create and run the server
178 | const server = new MockMCPServer(serverInfo, tools);
179 | server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/test/final-coverage-push.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Final Coverage Push - Simple targeted tests to reach 80% coverage
  3 |  * Focus on easy wins and edge cases
  4 |  */
  5 | 
  6 | import { describe, it, expect } from '@jest/globals';
  7 | import { DiscoveryEngine } from '../src/discovery/engine.js';
  8 | import { PersistentRAGEngine } from '../src/discovery/rag-engine.js';
  9 | 
 10 | describe('Final Coverage Push - Simple Tests', () => {
 11 |   describe('Discovery Engine Pattern Extraction', () => {
 12 |     it('should extract patterns from complex tool descriptions', async () => {
 13 |       const engine = new DiscoveryEngine();
 14 | 
 15 |       // Test with tool that has complex description patterns
 16 |       await engine.indexTool({
 17 |         id: 'complex:multi-operation-tool',
 18 |         name: 'multi-operation-tool',
 19 |         description: 'Create, read, update and delete multiple files in directory while executing commands and validating operations'
 20 |       });
 21 | 
 22 |       // Should extract multiple verb-object patterns
 23 |       const stats = engine.getStats();
 24 |       expect(stats.totalPatterns).toBeGreaterThan(5);
 25 |     });
 26 | 
 27 |     it('should handle pattern extraction edge cases', async () => {
 28 |       const engine = new DiscoveryEngine();
 29 | 
 30 |       // Test with empty and problematic descriptions
 31 |       const problematicTools = [
 32 |         {
 33 |           id: 'empty:desc',
 34 |           name: 'empty-desc',
 35 |           description: ''
 36 |         },
 37 |         {
 38 |           id: 'special:chars',
 39 |           name: 'special-chars',
 40 |           description: 'Tool with "quoted text" and (parentheses) and symbols @#$%'
 41 |         },
 42 |         {
 43 |           id: 'long:name-with-many-parts',
 44 |           name: 'very-long-tool-name-with-many-hyphenated-parts',
 45 |           description: 'Normal description'
 46 |         }
 47 |       ];
 48 | 
 49 |       for (const tool of problematicTools) {
 50 |         await engine.indexTool(tool);
 51 |       }
 52 | 
 53 |       // Should handle all cases without errors
 54 |       const stats = engine.getStats();
 55 |       expect(stats.totalTools).toBe(3);
 56 |     });
 57 | 
 58 |     it('should test findRelatedTools similarity calculation', async () => {
 59 |       const engine = new DiscoveryEngine();
 60 | 
 61 |       // Index multiple related tools
 62 |       const tools = [
 63 |         {
 64 |           id: 'fileops:read',
 65 |           name: 'fileops:read',
 66 |           description: 'Read file content from filesystem'
 67 |         },
 68 |         {
 69 |           id: 'fileops:write',
 70 |           name: 'fileops:write',
 71 |           description: 'Write file content to filesystem'
 72 |         },
 73 |         {
 74 |           id: 'mathops:calculate',
 75 |           name: 'mathops:calculate',
 76 |           description: 'Perform mathematical calculations'
 77 |         }
 78 |       ];
 79 | 
 80 |       for (const tool of tools) {
 81 |         await engine.indexTool(tool);
 82 |       }
 83 | 
 84 |       // Find related tools for the read operation
 85 |       const related = await engine.findRelatedTools('fileops:read');
 86 | 
 87 |       expect(related.length).toBeGreaterThan(0);
 88 |       // Should find write operation as related (similar description)
 89 |       const writeRelated = related.find(r => r.id === 'fileops:write');
 90 |       expect(writeRelated).toBeTruthy();
 91 |       expect(writeRelated?.similarity).toBeGreaterThan(0.3);
 92 |     });
 93 |   });
 94 | 
 95 |   describe('RAG Engine Basic Operations', () => {
 96 |     it('should handle minimal tool indexing', async () => {
 97 |       const ragEngine = new PersistentRAGEngine();
 98 |       await ragEngine.initialize();
 99 | 
100 |       // Index tool with minimal description
101 |       await ragEngine.indexMCP('minimal-server', [
102 |         {
103 |           id: 'minimal:tool',
104 |           name: 'tool',
105 |           description: 'x', // Very short description
106 |           inputSchema: {}
107 |         }
108 |       ]);
109 | 
110 |       // Test discovery with empty/minimal query
111 |       const results = await ragEngine.discover('', 5);
112 |       expect(Array.isArray(results)).toBe(true);
113 |     });
114 | 
115 |     it('should handle cache operations', async () => {
116 |       const ragEngine = new PersistentRAGEngine();
117 |       await ragEngine.initialize();
118 | 
119 |       // Index some tools
120 |       await ragEngine.indexMCP('test-server', [
121 |         {
122 |           id: 'test:refresh-tool',
123 |           name: 'refresh-tool',
124 |           description: 'Tool for testing cache refresh operations',
125 |           inputSchema: {}
126 |         }
127 |       ]);
128 | 
129 |       // Test cache refresh
130 |       await ragEngine.refreshCache();
131 | 
132 |       // Test cache clear
133 |       await ragEngine.clearCache();
134 | 
135 |       // Should still work after clear
136 |       const results = await ragEngine.discover('refresh', 1);
137 |       expect(Array.isArray(results)).toBe(true);
138 |     });
139 | 
140 |     it('should handle domain classification', async () => {
141 |       const ragEngine = new PersistentRAGEngine();
142 |       await ragEngine.initialize();
143 | 
144 |       // Test with query that contains multiple domain indicators
145 |       await ragEngine.indexMCP('multi-domain', [
146 |         {
147 |           id: 'multi:payment-file-web',
148 |           name: 'payment-file-web',
149 |           description: 'Process payment files on web server database',
150 |           inputSchema: {}
151 |         }
152 |       ]);
153 | 
154 |       const results = await ragEngine.discover('payment web database file', 3);
155 |       expect(results.length).toBeGreaterThanOrEqual(0);
156 |     });
157 |   });
158 | });
```

--------------------------------------------------------------------------------
/src/services/tool-finder.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Shared service for tool discovery and search
  3 |  * Handles pagination, filtering, and result organization
  4 |  */
  5 | 
  6 | import { NCPOrchestrator } from '../orchestrator/ncp-orchestrator.js';
  7 | 
  8 | export interface FindOptions {
  9 |   query?: string;
 10 |   page?: number;
 11 |   limit?: number;
 12 |   depth?: number;
 13 |   mcpFilter?: string | null;
 14 | }
 15 | 
 16 | export interface PaginationInfo {
 17 |   page: number;
 18 |   totalPages: number;
 19 |   totalResults: number;
 20 |   startIndex: number;
 21 |   endIndex: number;
 22 |   resultsInPage: number;
 23 | }
 24 | 
 25 | export interface GroupedTool {
 26 |   toolName: string;
 27 |   confidence: number;
 28 |   description?: string;
 29 |   schema?: any;
 30 | }
 31 | 
 32 | export interface FindResult {
 33 |   tools: any[];
 34 |   groupedByMCP: Record<string, GroupedTool[]>;
 35 |   pagination: PaginationInfo;
 36 |   mcpFilter: string | null;
 37 |   isListing: boolean;
 38 |   query: string;
 39 | }
 40 | 
 41 | export class ToolFinder {
 42 |   constructor(private orchestrator: NCPOrchestrator) {}
 43 | 
 44 |   /**
 45 |    * Main search method with all features
 46 |    */
 47 |   async find(options: FindOptions = {}): Promise<FindResult> {
 48 |     const {
 49 |       query = '',
 50 |       page = 1,
 51 |       limit = query ? 5 : 20,
 52 |       depth = 2,
 53 |       mcpFilter = null
 54 |     } = options;
 55 | 
 56 |     // Detect MCP-specific search if not explicitly provided
 57 |     const detectedMCPFilter = mcpFilter || this.detectMCPFilter(query);
 58 | 
 59 |     // Adjust search query based on MCP filter
 60 |     const searchQuery = detectedMCPFilter ? '' : query;
 61 | 
 62 |     // Get results with proper confidence-based ordering from orchestrator
 63 |     // Request enough for pagination but not excessive amounts
 64 |     const searchLimit = Math.min(1000, (page * limit) + 50); // Get enough for current page + buffer
 65 |     const allResults = await this.orchestrator.find(searchQuery, searchLimit, depth >= 1);
 66 | 
 67 |     // Apply MCP filtering if detected
 68 |     const filteredResults = detectedMCPFilter ?
 69 |       allResults.filter(r => r.mcpName.toLowerCase() === detectedMCPFilter.toLowerCase()) :
 70 |       allResults;
 71 | 
 72 |     // Results are already sorted by confidence from orchestrator - maintain that order
 73 |     // Calculate pagination
 74 |     const pagination = this.calculatePagination(filteredResults.length, page, limit);
 75 | 
 76 |     // Get page results while preserving confidence-based order
 77 |     const pageResults = filteredResults.slice(pagination.startIndex, pagination.endIndex);
 78 | 
 79 |     // Group by MCP
 80 |     const groupedByMCP = this.groupByMCP(pageResults);
 81 | 
 82 |     return {
 83 |       tools: pageResults,
 84 |       groupedByMCP,
 85 |       pagination,
 86 |       mcpFilter: detectedMCPFilter,
 87 |       isListing: !query || query.trim() === '',
 88 |       query
 89 |     };
 90 |   }
 91 | 
 92 |   /**
 93 |    * Calculate pagination details
 94 |    */
 95 |   private calculatePagination(totalResults: number, page: number, limit: number): PaginationInfo {
 96 |     const totalPages = Math.ceil(totalResults / limit);
 97 |     const safePage = Math.max(1, Math.min(page, totalPages || 1));
 98 |     const startIndex = (safePage - 1) * limit;
 99 |     const endIndex = Math.min(startIndex + limit, totalResults);
100 | 
101 |     return {
102 |       page: safePage,
103 |       totalPages,
104 |       totalResults,
105 |       startIndex,
106 |       endIndex,
107 |       resultsInPage: endIndex - startIndex
108 |     };
109 |   }
110 | 
111 |   /**
112 |    * Group tools by their MCP
113 |    */
114 |   private groupByMCP(results: any[]): Record<string, GroupedTool[]> {
115 |     const groups: Record<string, GroupedTool[]> = {};
116 | 
117 |     results.forEach(result => {
118 |       if (!groups[result.mcpName]) {
119 |         groups[result.mcpName] = [];
120 |       }
121 |       groups[result.mcpName].push({
122 |         toolName: result.toolName,
123 |         confidence: result.confidence,
124 |         description: result.description,
125 |         schema: result.schema
126 |       });
127 |     });
128 | 
129 |     return groups;
130 |   }
131 | 
132 |   /**
133 |    * Detect if query is an MCP-specific search
134 |    */
135 |   private detectMCPFilter(query: string): string | null {
136 |     if (!query) return null;
137 | 
138 |     const lowerQuery = query.toLowerCase().trim();
139 | 
140 |     // Common MCP names to check
141 |     const knownMCPs = [
142 |       'filesystem', 'memory', 'shell', 'portel', 'tavily',
143 |       'desktop-commander', 'stripe', 'sequential-thinking',
144 |       'context7-mcp', 'github', 'gitlab', 'slack'
145 |     ];
146 | 
147 |     // Check for exact MCP name match
148 |     for (const mcp of knownMCPs) {
149 |       if (lowerQuery === mcp || lowerQuery === `${mcp}:`) {
150 |         return mcp;
151 |       }
152 |     }
153 | 
154 |     // Check if query starts with MCP:tool pattern
155 |     if (lowerQuery.includes(':')) {
156 |       const [potentialMCP] = lowerQuery.split(':');
157 |       if (knownMCPs.includes(potentialMCP)) {
158 |         return potentialMCP;
159 |       }
160 |     }
161 | 
162 |     return null;
163 |   }
164 | 
165 |   /**
166 |    * Get sample tools when no results found
167 |    */
168 |   async getSampleTools(count: number = 8): Promise<{ mcpName: string; description: string }[]> {
169 |     const sampleTools = await this.orchestrator.find('', count);
170 |     const mcpSet = new Set<string>();
171 |     const samples: { mcpName: string; description: string }[] = [];
172 | 
173 |     for (const tool of sampleTools) {
174 |       if (!mcpSet.has(tool.mcpName)) {
175 |         mcpSet.add(tool.mcpName);
176 |         samples.push({
177 |           mcpName: tool.mcpName,
178 |           description: tool.mcpName // TODO: Get from MCP server info
179 |         });
180 |       }
181 |     }
182 | 
183 |     return samples;
184 |   }
185 | 
186 | }
```

--------------------------------------------------------------------------------
/test/orchestrator-simple-branches.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Simple Orchestrator Branch Coverage Tests
  3 |  * Target key uncovered branches without complex mocking
  4 |  */
  5 | 
  6 | import { describe, it, expect, beforeEach, jest } from '@jest/globals';
  7 | import { NCPOrchestrator } from '../src/orchestrator/ncp-orchestrator';
  8 | import * as fs from 'fs';
  9 | 
 10 | jest.mock('fs');
 11 | 
 12 | describe('Orchestrator Simple Branch Tests', () => {
 13 |   let orchestrator: NCPOrchestrator;
 14 |   const mockFs = fs as jest.Mocked<typeof fs>;
 15 | 
 16 |   beforeEach(() => {
 17 |     jest.clearAllMocks();
 18 |     orchestrator = new NCPOrchestrator('test');
 19 |     mockFs.existsSync.mockReturnValue(false);
 20 |   });
 21 | 
 22 |   describe('Error Path Coverage', () => {
 23 |     it('should handle MCP not configured error', async () => {
 24 |       // Set up minimal profile
 25 |       const emptyProfile = { name: 'test', mcpServers: {} };
 26 |       mockFs.existsSync.mockReturnValue(true);
 27 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(emptyProfile) as any);
 28 | 
 29 |       await orchestrator.initialize();
 30 | 
 31 |       // Try to run tool from unconfigured MCP - should trigger line 419
 32 |       const result = await orchestrator.run('nonexistent:tool', {});
 33 | 
 34 |       expect(result.success).toBe(false);
 35 |       expect(result.error).toContain('not found');
 36 |     });
 37 | 
 38 |     it('should handle initialization with no profile', async () => {
 39 |       // Profile file doesn't exist
 40 |       mockFs.existsSync.mockReturnValue(false);
 41 | 
 42 |       // Should not throw
 43 |       await expect(orchestrator.initialize()).resolves.not.toThrow();
 44 |     });
 45 | 
 46 |     it('should handle find with empty query', async () => {
 47 |       const profile = { name: 'test', mcpServers: {} };
 48 |       mockFs.existsSync.mockReturnValue(true);
 49 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
 50 | 
 51 |       await orchestrator.initialize();
 52 | 
 53 |       // Empty query should return empty results (line 276-277)
 54 |       const results = await orchestrator.find('', 5);
 55 |       expect(Array.isArray(results)).toBe(true);
 56 |     });
 57 | 
 58 |     it('should handle getAllResources with no MCPs', async () => {
 59 |       const profile = { name: 'test', mcpServers: {} };
 60 |       mockFs.existsSync.mockReturnValue(true);
 61 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
 62 | 
 63 |       await orchestrator.initialize();
 64 | 
 65 |       // Should return empty array
 66 |       const resources = await orchestrator.getAllResources();
 67 |       expect(Array.isArray(resources)).toBe(true);
 68 |       expect(resources).toEqual([]);
 69 |     });
 70 | 
 71 |     it('should handle tool execution with invalid format', async () => {
 72 |       const profile = { name: 'test', mcpServers: {} };
 73 |       mockFs.existsSync.mockReturnValue(true);
 74 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
 75 | 
 76 |       await orchestrator.initialize();
 77 | 
 78 |       // Should handle invalid tool format
 79 |       const result = await orchestrator.run('invalid-format', {});
 80 |       expect(result.success).toBe(false);
 81 |     });
 82 | 
 83 |     it('should handle getAllPrompts with no MCPs', async () => {
 84 |       const profile = { name: 'test', mcpServers: {} };
 85 |       mockFs.existsSync.mockReturnValue(true);
 86 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
 87 | 
 88 |       await orchestrator.initialize();
 89 | 
 90 |       // Should return empty array
 91 |       const prompts = await orchestrator.getAllPrompts();
 92 |       expect(Array.isArray(prompts)).toBe(true);
 93 |       expect(prompts).toEqual([]);
 94 |     });
 95 | 
 96 |     it('should handle multiple initialization calls safely', async () => {
 97 |       const profile = { name: 'test', mcpServers: {} };
 98 |       mockFs.existsSync.mockReturnValue(true);
 99 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
100 | 
101 |       // Multiple calls should be safe
102 |       await orchestrator.initialize();
103 |       await orchestrator.initialize();
104 |       await orchestrator.initialize();
105 | 
106 |       // Should not throw
107 |       expect(true).toBe(true);
108 |     });
109 | 
110 |     it('should handle cleanup gracefully', async () => {
111 |       // Should not throw even without initialization
112 |       await expect(orchestrator.cleanup()).resolves.not.toThrow();
113 |     });
114 |   });
115 | 
116 |   describe('Cache Edge Cases', () => {
117 |     it('should handle corrupted cache file', async () => {
118 |       const profile = { name: 'test', mcpServers: {} };
119 | 
120 |       // Profile exists but cache is corrupted
121 |       mockFs.existsSync.mockImplementation((path: any) => {
122 |         return path.toString().includes('.json');
123 |       });
124 | 
125 |       mockFs.readFileSync.mockImplementation((path: any) => {
126 |         if (path.toString().includes('profiles')) {
127 |           return JSON.stringify(profile) as any;
128 |         } else {
129 |           return 'corrupted cache data' as any;
130 |         }
131 |       });
132 | 
133 |       // Should handle corrupted cache gracefully
134 |       await expect(orchestrator.initialize()).resolves.not.toThrow();
135 |     });
136 | 
137 |     it('should handle cache save failure', async () => {
138 |       const profile = { name: 'test', mcpServers: {} };
139 |       mockFs.existsSync.mockReturnValue(true);
140 |       mockFs.readFileSync.mockReturnValue(JSON.stringify(profile) as any);
141 | 
142 |       // Mock writeFileSync to throw
143 |       mockFs.writeFileSync.mockImplementation(() => {
144 |         throw new Error('Write failed');
145 |       });
146 | 
147 |       // Should handle write failure gracefully
148 |       await expect(orchestrator.initialize()).resolves.not.toThrow();
149 |     });
150 |   });
151 | });
```

--------------------------------------------------------------------------------
/src/utils/update-checker.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Update Checker for NCP
  3 |  * Checks for new versions and notifies users
  4 |  */
  5 | 
  6 | import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
  7 | import { join } from 'path';
  8 | import { homedir } from 'os';
  9 | import chalk from 'chalk';
 10 | import { version as packageVersion, packageName } from './version.js';
 11 | 
 12 | interface UpdateCheckResult {
 13 |   hasUpdate: boolean;
 14 |   currentVersion: string;
 15 |   latestVersion?: string;
 16 |   updateAvailable?: boolean;
 17 | }
 18 | 
 19 | interface UpdateCache {
 20 |   lastCheck: number;
 21 |   latestVersion: string;
 22 |   notificationShown: boolean;
 23 | }
 24 | 
 25 | export class UpdateChecker {
 26 |   private packageVersion: string;
 27 |   private packageName: string;
 28 |   private cacheFile: string;
 29 |   private readonly checkInterval = 24 * 60 * 60 * 1000; // 24 hours
 30 | 
 31 |   constructor() {
 32 |     // Use package info from module-level constants (read from package.json)
 33 |     this.packageName = packageName;
 34 |     this.packageVersion = packageVersion;
 35 | 
 36 |     // Cache file location
 37 |     const ncpDir = join(homedir(), '.ncp');
 38 |     if (!existsSync(ncpDir)) {
 39 |       mkdirSync(ncpDir, { recursive: true });
 40 |     }
 41 |     this.cacheFile = join(ncpDir, 'update-cache.json');
 42 |   }
 43 | 
 44 |   private loadCache(): UpdateCache | null {
 45 |     try {
 46 |       if (!existsSync(this.cacheFile)) {
 47 |         return null;
 48 |       }
 49 |       return JSON.parse(readFileSync(this.cacheFile, 'utf8'));
 50 |     } catch {
 51 |       return null;
 52 |     }
 53 |   }
 54 | 
 55 |   private saveCache(cache: UpdateCache): void {
 56 |     try {
 57 |       writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2));
 58 |     } catch {
 59 |       // Ignore cache write errors
 60 |     }
 61 |   }
 62 | 
 63 |   private async fetchLatestVersion(): Promise<string | null> {
 64 |     try {
 65 |       // Add timeout to prevent hanging
 66 |       const controller = new AbortController();
 67 |       const timeout = setTimeout(() => controller.abort(), 3000); // 3 second timeout
 68 | 
 69 |       const response = await fetch(`https://registry.npmjs.org/${this.packageName}/latest`, {
 70 |         signal: controller.signal
 71 |       });
 72 | 
 73 |       clearTimeout(timeout);
 74 | 
 75 |       if (!response.ok) {
 76 |         return null;
 77 |       }
 78 |       const data = await response.json();
 79 |       return data.version || null;
 80 |     } catch {
 81 |       return null;
 82 |     }
 83 |   }
 84 | 
 85 |   private compareVersions(current: string, latest: string): boolean {
 86 |     // Simple semantic version comparison
 87 |     const parseVersion = (v: string) => v.split('.').map(num => parseInt(num, 10));
 88 |     const currentParts = parseVersion(current);
 89 |     const latestParts = parseVersion(latest);
 90 | 
 91 |     for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
 92 |       const currentPart = currentParts[i] || 0;
 93 |       const latestPart = latestParts[i] || 0;
 94 | 
 95 |       if (latestPart > currentPart) return true;
 96 |       if (latestPart < currentPart) return false;
 97 |     }
 98 |     return false;
 99 |   }
100 | 
101 |   async checkForUpdates(forceCheck = false): Promise<UpdateCheckResult> {
102 |     const cache = this.loadCache();
103 |     const now = Date.now();
104 | 
105 |     // Check if we need to fetch (force check or cache expired)
106 |     const shouldCheck = forceCheck ||
107 |       !cache ||
108 |       (now - cache.lastCheck) > this.checkInterval;
109 | 
110 |     let latestVersion = cache?.latestVersion;
111 | 
112 |     if (shouldCheck) {
113 |       const fetchedVersion = await this.fetchLatestVersion();
114 |       if (fetchedVersion) {
115 |         latestVersion = fetchedVersion;
116 | 
117 |         // Save to cache
118 |         this.saveCache({
119 |           lastCheck: now,
120 |           latestVersion: fetchedVersion,
121 |           notificationShown: false
122 |         });
123 |       }
124 |     }
125 | 
126 |     const hasUpdate = latestVersion ? this.compareVersions(this.packageVersion, latestVersion) : false;
127 | 
128 |     return {
129 |       hasUpdate,
130 |       currentVersion: this.packageVersion,
131 |       latestVersion,
132 |       updateAvailable: hasUpdate
133 |     };
134 |   }
135 | 
136 |   async showUpdateNotification(): Promise<void> {
137 |     const cache = this.loadCache();
138 |     if (cache?.notificationShown) {
139 |       return; // Already shown for this version
140 |     }
141 | 
142 |     const result = await this.checkForUpdates();
143 |     if (result.hasUpdate && result.latestVersion) {
144 |       console.log();
145 |       console.log(chalk.yellow('📦 Update Available!'));
146 |       console.log(chalk.dim(`   Current: ${result.currentVersion}`));
147 |       console.log(chalk.green(`   Latest:  ${result.latestVersion}`));
148 |       console.log();
149 |       console.log(chalk.cyan('   Run: npm install -g @portel/ncp@latest'));
150 |       console.log(chalk.dim('   Or:  ncp update'));
151 |       console.log();
152 | 
153 |       // Mark notification as shown
154 |       if (cache) {
155 |         cache.notificationShown = true;
156 |         this.saveCache(cache);
157 |       }
158 |     }
159 |   }
160 | 
161 |   async performUpdate(): Promise<boolean> {
162 |     try {
163 |       const { spawn } = await import('child_process');
164 | 
165 |       console.log(chalk.blue('🔄 Updating NCP...'));
166 | 
167 |       return new Promise((resolve) => {
168 |         const updateProcess = spawn('npm', ['install', '-g', '@portel/ncp@latest'], {
169 |           stdio: 'inherit'
170 |         });
171 | 
172 |         updateProcess.on('close', (code) => {
173 |           if (code === 0) {
174 |             console.log(chalk.green('✅ NCP updated successfully!'));
175 |             console.log(chalk.dim('   Restart your terminal or run: source ~/.bashrc'));
176 |             resolve(true);
177 |           } else {
178 |             console.log(chalk.red('❌ Update failed. Please try manually:'));
179 |             console.log(chalk.dim('   npm install -g @portel/ncp@latest'));
180 |             resolve(false);
181 |           }
182 |         });
183 |       });
184 |     } catch (error) {
185 |       console.log(chalk.red('❌ Update failed:'), error);
186 |       return false;
187 |     }
188 |   }
189 | }
```
Page 2/12FirstPrevNextLast