This is page 1 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 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- ``` 1 | # Development files 2 | node_modules 3 | .git 4 | .env 5 | *.log 6 | .DS_Store 7 | 8 | # Build output (will be generated during build) 9 | dist 10 | 11 | # NCP runtime files 12 | .ncp/cache 13 | .ncp/embeddings.json 14 | .ncp/embeddings-metadata.json 15 | 16 | # Testing and development 17 | test/ 18 | tests/ 19 | *.test.js 20 | *.test.ts 21 | coverage/ ``` -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "git": { 3 | "commitMessage": "release: ${version}", 4 | "tagName": "${version}" 5 | }, 6 | "github": { 7 | "release": true, 8 | "releaseName": "Release ${version}" 9 | }, 10 | "npm": { 11 | "publish": true 12 | }, 13 | "plugins": { 14 | "@release-it/conventional-changelog": { 15 | "preset": "conventionalcommits", 16 | "infile": "CHANGELOG.md" 17 | } 18 | } 19 | } ``` -------------------------------------------------------------------------------- /.dxtignore: -------------------------------------------------------------------------------- ``` 1 | # Development files 2 | .git/ 3 | .github/ 4 | coverage/ 5 | test/ 6 | docs/ 7 | scripts/ 8 | 9 | # Cache and user data 10 | .ncp/ 11 | *.cache 12 | *.tmp 13 | 14 | # Source files (only need compiled dist/) 15 | src/ 16 | *.ts 17 | tsconfig.json 18 | jest.config.js 19 | 20 | # CLI-specific code (MCP-only bundle uses index-mcp.js) 21 | dist/cli/ 22 | dist/index.js 23 | dist/index.js.map 24 | 25 | # Documentation 26 | *.md 27 | !manifest.json 28 | 29 | # Build artifacts 30 | *.tgz 31 | *.dxt 32 | *.backup 33 | 34 | # IDE 35 | .vscode/ 36 | .idea/ 37 | *.swp 38 | 39 | # Development configs 40 | .eslintrc* 41 | .prettierrc* 42 | .travis.yml 43 | .dockerignore 44 | 45 | # Other unnecessary files 46 | CHANGELOG.md 47 | CONTRIBUTING.md 48 | CODE_OF_CONDUCT.md 49 | LICENSE 50 | .release-it.json 51 | .npmignore 52 | ``` -------------------------------------------------------------------------------- /.mcpbignore: -------------------------------------------------------------------------------- ``` 1 | # Development files 2 | .git/ 3 | .github/ 4 | coverage/ 5 | test/ 6 | docs/ 7 | scripts/ 8 | 9 | # Cache and user data 10 | .ncp/ 11 | *.cache 12 | *.tmp 13 | 14 | # Source files (only need compiled dist/) 15 | src/ 16 | *.ts 17 | tsconfig.json 18 | jest.config.js 19 | 20 | # CLI-specific code (MCP-only bundle uses index-mcp.js) 21 | dist/cli/ 22 | dist/index.js 23 | dist/index.js.map 24 | 25 | # Documentation 26 | *.md 27 | !manifest.json 28 | 29 | # Build artifacts 30 | *.tgz 31 | *.dxt 32 | *.backup 33 | 34 | # IDE 35 | .vscode/ 36 | .idea/ 37 | *.swp 38 | 39 | # Development configs 40 | .eslintrc* 41 | .prettierrc* 42 | .travis.yml 43 | .dockerignore 44 | 45 | # Other unnecessary files 46 | CHANGELOG.md 47 | CONTRIBUTING.md 48 | CODE_OF_CONDUCT.md 49 | LICENSE 50 | .release-it.json 51 | .npmignore 52 | ``` -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- ``` 1 | # Source code and development files 2 | src/ 3 | test/ 4 | scripts/ 5 | coverage/ 6 | docs/ 7 | .github/ 8 | *.ts 9 | !dist/**/*.d.ts 10 | 11 | # Build artifacts that shouldn't be published 12 | *.map 13 | *.tsbuildinfo 14 | 15 | # Configuration files 16 | .gitignore 17 | .npmignore 18 | tsconfig.json 19 | jest.config.js 20 | .release-it.json 21 | .editorconfig 22 | .prettierrc* 23 | .eslintrc* 24 | 25 | # Documentation (keep only README.md and LICENSE) 26 | CHANGELOG.md 27 | CODE_OF_CONDUCT.md 28 | CONTRIBUTING.md 29 | SECURITY.md 30 | RELEASE.md 31 | *.prd.md 32 | 33 | # Development and testing artifacts 34 | *.test.js 35 | *.spec.js 36 | *.test.ts 37 | *.spec.ts 38 | **/testing/ 39 | **/__tests__/ 40 | **/__mocks__/ 41 | 42 | # Local and temporary files 43 | .env* 44 | *.local.* 45 | *.tmp 46 | *.temp 47 | *.backup.* 48 | .DS_Store 49 | *.log 50 | 51 | # Sensitive files and credentials 52 | .mcpregistry_* 53 | *_token 54 | *_secret 55 | *.key 56 | *.pem 57 | 58 | # Local runtime data 59 | .ncp/ 60 | *.cache 61 | 62 | # Uncomment if you don't need TypeScript support 63 | # dist/**/*.d.ts 64 | 65 | # Example and reference files (not needed for runtime) 66 | MCP-CONFIG-*.ts 67 | MCP-CONFIG-*.json 68 | MCP-CONFIGURATION-*.json 69 | *-EXAMPLE.* 70 | 71 | # IDE files 72 | .vscode/ 73 | .idea/ 74 | *.swp 75 | *.swo 76 | 77 | # Images and media (not needed for runtime) 78 | *.png 79 | *.jpg 80 | *.jpeg 81 | *.gif 82 | *.svg 83 | 84 | # Specific files not needed in package 85 | .dockerignore 86 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | dist/ 4 | coverage/ 5 | 6 | # Build artifacts 7 | *.mcpb 8 | *.dxt 9 | *.tgz 10 | 11 | # Local configuration 12 | .env 13 | .env.local 14 | *.local.* 15 | .claude.local.md 16 | 17 | # Sensitive tokens and credentials 18 | .mcpregistry_* 19 | *_token 20 | *_secret 21 | *.key 22 | *.pem 23 | 24 | # Cache and temp files 25 | .ncp/ 26 | *.cache 27 | *.tmp 28 | *.temp 29 | *.backup.* 30 | 31 | # AI and draft content 32 | *.prd.md 33 | *.draft.md 34 | *.ai.md 35 | *.notes.md 36 | *.temp.md 37 | 38 | # Session documentation (move to commercial repo) 39 | SESSION-*.md 40 | IMPLEMENTATION-*.md 41 | CLEANUP-*.md 42 | TEST-PLAN.md 43 | TESTING-*.md 44 | 45 | # Test and script files 46 | *.test.js 47 | *.script.js 48 | !jest.config.js 49 | !test/**/*.test.ts 50 | 51 | # IDE 52 | .idea/ 53 | .vscode/ 54 | *.swp 55 | *.swo 56 | *~ 57 | .DS_Store 58 | 59 | # Logs 60 | *.log 61 | npm-debug.log* 62 | yarn-debug.log* 63 | yarn-error.log* 64 | 65 | # Scripts directory (development files not for release) 66 | scripts/ 67 | !scripts/*.cjs 68 | !scripts/cleanup/ 69 | 70 | # Ignore image files in project root (temporary screenshots) 71 | /*.png 72 | /*.jpg 73 | /*.jpeg 74 | /*.gif 75 | 76 | # Keep documentation images 77 | !docs/images/** 78 | 79 | # User runtime data (created when users run NCP) 80 | .ncp/ 81 | ~/.ncp/ 82 | 83 | # Development artifacts (use ncp-dev-workspace instead) 84 | /profiles/ 85 | /ecosystem-repo-setup/ 86 | test-*.js 87 | experiment-*.js 88 | *-ecosystem.json 89 | 90 | # Date-prefixed AI exports 91 | 2025-*.txt ``` -------------------------------------------------------------------------------- /docs/clients/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # NCP Installation Guides by Client 2 | 3 | Choose your MCP client below for detailed installation instructions. 4 | 5 | --- 6 | 7 | ## 🎯 Supported Clients 8 | 9 | ### 🖥️ [Claude Desktop](./claude-desktop.md) 10 | **Installation Methods:** Extension (.dxt) + JSON Config 11 | 12 | **Features:** 13 | - ✅ One-click extension installation 14 | - ✅ Auto-import of existing MCPs 15 | - ✅ Auto-sync on every startup 16 | - ✅ Both CLI and extension modes supported 17 | 18 | **Best for:** Most users, production use 19 | 20 | [→ Read Claude Desktop Guide](./claude-desktop.md) 21 | 22 | --- 23 | 24 | ### 🔍 [Perplexity](./perplexity.md) 25 | **Installation Methods:** JSON Config only 26 | 27 | **Features:** 28 | - ✅ Manual JSON configuration 29 | - ⚠️ No auto-import yet (coming soon) 30 | - ⚠️ .dxt extension support coming soon 31 | 32 | **Best for:** Perplexity Mac app users 33 | 34 | [→ Read Perplexity Guide](./perplexity.md) 35 | 36 | --- 37 | 38 | ### 💻 [Cursor IDE](./cursor.md) 39 | **Installation Methods:** JSON Config only 40 | 41 | **Features:** 42 | - ✅ JSON configuration via Cline settings 43 | - ✅ Works with Cursor's AI features 44 | - ✅ Standard MCP integration 45 | 46 | **Best for:** Cursor IDE users 47 | 48 | [→ Read Cursor Guide](./cursor.md) 49 | 50 | --- 51 | 52 | ### 🔧 [Cline (VS Code)](./cline.md) 53 | **Installation Methods:** JSON Config only 54 | 55 | **Features:** 56 | - ✅ VS Code extension integration 57 | - ✅ JSON configuration 58 | - ✅ Works with Claude API 59 | 60 | **Best for:** VS Code + Cline users 61 | 62 | [→ Read Cline Guide](./cline.md) 63 | 64 | --- 65 | 66 | ### ⚡ [Continue (VS Code)](./continue.md) 67 | **Installation Methods:** JSON Config only 68 | 69 | **Features:** 70 | - ✅ VS Code extension integration 71 | - ✅ Nested experimental config format 72 | - ✅ Works with multiple AI models 73 | 74 | **Best for:** VS Code + Continue users 75 | 76 | [→ Read Continue Guide](./continue.md) 77 | 78 | --- 79 | 80 | ## 🆚 Installation Method Comparison 81 | 82 | | Client | Extension (.dxt) | JSON Config | Auto-Import | 83 | |--------|-----------------|-------------|-------------| 84 | | **Claude Desktop** | ✅ Recommended | ✅ Available | ✅ Yes | 85 | | **Perplexity** | ⏳ Coming Soon | ✅ Available | ⏳ Coming Soon | 86 | | **Cursor** | ❌ Not Supported | ✅ Available | ❌ No | 87 | | **Cline** | ❌ Not Supported | ✅ Available | ❌ No | 88 | | **Continue** | ❌ Not Supported | ✅ Available | ❌ No | 89 | 90 | --- 91 | 92 | ## 📋 Quick Start by Client 93 | 94 | ### Claude Desktop (Recommended) 95 | ```bash 96 | # Download and drag-drop ncp.dxt 97 | # OR use JSON config: 98 | npm install -g @portel/ncp 99 | ncp config import 100 | # Edit claude_desktop_config.json to use NCP 101 | ``` 102 | 103 | ### All Other Clients 104 | ```bash 105 | # 1. Install NCP 106 | npm install -g @portel/ncp 107 | 108 | # 2. Add your MCPs 109 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 110 | ncp add github npx @modelcontextprotocol/server-github 111 | 112 | # 3. Configure your client's JSON config 113 | # (See client-specific guide for config file location) 114 | 115 | # 4. Restart your client 116 | ``` 117 | 118 | --- 119 | 120 | ## 🎯 Which Installation Method Should I Use? 121 | 122 | ### Use Extension (.dxt) Installation If: 123 | - ✅ You're using Claude Desktop 124 | - ✅ You want one-click installation 125 | - ✅ You want automatic MCP detection 126 | - ✅ You want auto-sync on startup 127 | 128 | ### Use JSON Configuration If: 129 | - ✅ Your client doesn't support .dxt yet 130 | - ✅ You prefer manual control 131 | - ✅ You need custom profile setups 132 | - ✅ You're testing or developing 133 | 134 | --- 135 | 136 | ## 🔮 Future Support 137 | 138 | Clients we're tracking for future .dxt support: 139 | - 🔜 **Perplexity** - Testing .dxt drag-and-drop 140 | - 🔜 **Cursor** - Investigating extension support 141 | - 🔜 **Windsurf** - Monitoring for MCP support 142 | - 🔜 **Zed** - Awaiting official MCP integration 143 | 144 | Want to see NCP support for another client? [Open a feature request](https://github.com/portel-dev/ncp/issues/new?template=feature_request.yml) 145 | 146 | --- 147 | 148 | ## 🤝 Contributing Client Guides 149 | 150 | Found an issue or want to improve a guide? 151 | 152 | 1. **Report issues:** [GitHub Issues](https://github.com/portel-dev/ncp/issues) 153 | 2. **Suggest improvements:** [GitHub Discussions](https://github.com/portel-dev/ncp/discussions) 154 | 3. **Submit PR:** [Contributing Guide](../../CONTRIBUTING.md) 155 | 156 | --- 157 | 158 | ## 📚 Additional Resources 159 | 160 | - **[Main README](../../README.md)** - Overview and features 161 | - **[How It Works](../guides/how-it-works.md)** - Technical architecture 162 | - **[Testing Guide](../guides/testing.md)** - Verification steps 163 | - **[Troubleshooting](../../README.md#-troubleshooting)** - Common issues 164 | 165 | --- 166 | 167 | ## 📍 Quick Links 168 | 169 | ### Configuration File Locations 170 | 171 | **Claude Desktop:** 172 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 173 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 174 | - Linux: `~/.config/Claude/claude_desktop_config.json` 175 | 176 | **Cursor/Cline:** 177 | - macOS: `~/Library/Application Support/[Client]/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 178 | - Windows: `%APPDATA%/[Client]/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 179 | - Linux: `~/.config/[Client]/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 180 | 181 | **Continue:** 182 | - All platforms: `~/.continue/config.json` 183 | 184 | **Perplexity:** 185 | - macOS: `~/Library/Containers/ai.perplexity.mac/Data/Documents/mcp_servers` 186 | 187 | **NCP Profiles:** 188 | - All platforms: `~/.ncp/profiles/` 189 | 190 | --- 191 | 192 | **Questions?** Ask in [GitHub Discussions](https://github.com/portel-dev/ncp/discussions) 193 | ``` -------------------------------------------------------------------------------- /README.new.md: -------------------------------------------------------------------------------- ```markdown 1 | # NCP - Your AI's Personal Assistant 2 | 3 | [](https://www.npmjs.com/package/@portel/ncp) 4 | [](https://www.npmjs.com/package/@portel/ncp) 5 | [](https://github.com/portel-dev/ncp/releases) 6 | [](https://www.elastic.co/licensing/elastic-license) 7 | [](https://modelcontextprotocol.io/) 8 | 9 | <!-- mcp-name: io.github.portel-dev/ncp --> 10 | 11 | --- 12 | 13 | ## 🎯 **One Line That Changes Everything** 14 | 15 | **Your AI doesn't see your 50 tools. It dreams of the perfect tool, and NCP finds it instantly.** 16 | 17 | That's it. That's NCP. 18 | 19 | --- 20 | 21 | ## 😫 **The Problem** 22 | 23 | You installed 10 MCPs to supercharge your AI. Instead: 24 | 25 | - **AI becomes indecisive** ("Should I use `read_file` or `get_file_content`?") 26 | - **Conversations end early** (50 tool schemas = 100k+ tokens before work starts) 27 | - **Wrong tools picked** (AI confused by similar-sounding options) 28 | - **Computer works harder** (all MCPs running constantly, most idle) 29 | 30 | **The paradox:** More tools = Less productivity. 31 | 32 | > **What's MCP?** The [Model Context Protocol](https://modelcontextprotocol.io) by Anthropic lets AI assistants connect to external tools. Think of MCPs as "plugins" that give your AI superpowers. 33 | 34 | --- 35 | 36 | ## ✨ **The Solution: Six Stories** 37 | 38 | Every NCP feature solves a real problem. Here's how: 39 | 40 | ### **[🌟 Story 1: Dream and Discover](docs/stories/01-dream-and-discover.md)** *2 min* 41 | > **Problem:** AI overwhelmed by 50+ tool schemas 42 | > **Solution:** AI writes what it needs, NCP finds the perfect tool 43 | > **Result:** 97% fewer tokens, 5x faster, AI becomes decisive 44 | 45 | ### **[🔐 Story 2: Secrets in Plain Sight](docs/stories/02-secrets-in-plain-sight.md)** *2 min* 46 | > **Problem:** API keys exposed in AI chat logs forever 47 | > **Solution:** Clipboard handshake keeps secrets server-side 48 | > **Result:** AI never sees your tokens, full security + convenience 49 | 50 | ### **[🔄 Story 3: Sync and Forget](docs/stories/03-sync-and-forget.md)** *2 min* 51 | > **Problem:** Configure same MCPs twice (Claude Desktop + NCP) 52 | > **Solution:** NCP auto-syncs from Claude Desktop on every startup 53 | > **Result:** Zero manual configuration, always in sync 54 | 55 | ### **[📦 Story 4: Double-Click Install](docs/stories/04-double-click-install.md)** *2 min* 56 | > **Problem:** Installing MCPs requires terminal, npm, JSON editing 57 | > **Solution:** Download .mcpb → Double-click → Done 58 | > **Result:** 30-second install, feels like native app 59 | 60 | ### **[🕵️ Story 5: Runtime Detective](docs/stories/05-runtime-detective.md)** *2 min* 61 | > **Problem:** MCPs break when Claude Desktop runtime changes 62 | > **Solution:** NCP detects runtime dynamically on every boot 63 | > **Result:** Adapts automatically, no version mismatches 64 | 65 | ### **[🌐 Story 6: Official Registry](docs/stories/06-official-registry.md)** *2 min* 66 | > **Problem:** Finding right MCP takes hours of Googling 67 | > **Solution:** AI searches 2,200+ MCPs from official registry 68 | > **Result:** Discovery through conversation, install in seconds 69 | 70 | **Read all six stories: 12 minutes total.** You'll understand exactly why NCP transforms how you work with MCPs. 71 | 72 | --- 73 | 74 | ## 🚀 **Quick Start** 75 | 76 | ### **Option 1: Claude Desktop Users** (Recommended) 77 | 78 | 1. Download [ncp.mcpb](https://github.com/portel-dev/ncp/releases/latest/download/ncp.mcpb) 79 | 2. Double-click the file 80 | 3. Click "Install" when Claude Desktop prompts 81 | 4. **Done!** NCP auto-syncs all your Claude Desktop MCPs 82 | 83 | **Time:** 30 seconds | **Difficulty:** Zero | **Story:** [Double-Click Install →](docs/stories/04-double-click-install.md) 84 | 85 | --- 86 | 87 | ### **Option 2: All Other Clients** (Cursor, Cline, Continue, etc.) 88 | 89 | ```bash 90 | # 1. Install NCP 91 | npm install -g @portel/ncp 92 | 93 | # 2. Import existing MCPs (copy your config to clipboard first) 94 | ncp config import 95 | 96 | # 3. Configure your AI client 97 | { 98 | "mcpServers": { 99 | "ncp": { 100 | "command": "ncp" 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | **Time:** 2 minutes | **Difficulty:** Copy-paste | **Full Guide:** [Installation →](#installation) 107 | 108 | --- 109 | 110 | ## 📊 **The Difference (Numbers)** 111 | 112 | | Your MCP Setup | Without NCP | With NCP | Improvement | 113 | |----------------|-------------|----------|-------------| 114 | | **Tokens used** | 100,000+ (tool schemas) | 2,500 (2 tools) | **97% saved** | 115 | | **AI response time** | 8 seconds (analyzing) | <1 second (instant) | **8x faster** | 116 | | **Wrong tool selection** | 30% of attempts | <3% of attempts | **10x accuracy** | 117 | | **Conversation length** | 50 messages (context limit) | 600+ messages | **12x longer** | 118 | | **Computer CPU usage** | High (all MCPs running) | Low (on-demand loading) | **~70% saved** | 119 | 120 | **Real measurements from production usage.** Your mileage may vary, but the pattern holds: NCP makes AI faster, smarter, cheaper. 121 | 122 | --- 123 | 124 | ## 📚 **Learn More** 125 | 126 | ### **For Users:** 127 | - 🎯 **[The Six Stories](docs/stories/)** - Understand NCP through narratives (12 min) 128 | - 🔧 **[Installation Guide](#installation-full-guide)** - Detailed setup for all platforms 129 | - 🧪 **[Test Drive](#test-drive)** - Try NCP CLI to see what AI experiences 130 | - 🛟 **[Troubleshooting](#troubleshooting)** - Fix common issues 131 | 132 | ### **For Developers:** 133 | - 📖 **[How It Works](HOW-IT-WORKS.md)** - Technical deep dive 134 | - 🏗️ **[Architecture](STORY-DRIVEN-DOCUMENTATION.md)** - System design and stories 135 | - 🤝 **[Contributing](CONTRIBUTING.md)** - Help make NCP better 136 | - 📝 **[Feature Stories](.github/FEATURE_STORY_TEMPLATE.md)** - Propose new features 137 | 138 | ### **For Teams:** 139 | - 🚀 **[Project-Level Config](#project-level-configuration)** - Per-project MCPs 140 | - 👥 **[Team Workflows](docs/stories/03-sync-and-forget.md)** - Consistent setup 141 | - 🔐 **[Security Pattern](docs/stories/02-secrets-in-plain-sight.md)** - Safe credential handling 142 | 143 | --- 144 | 145 | ## 🎓 **What People Say** 146 | 147 | > "NCP does not expose any tools to AI. Instead, it lets the AI dream of a tool and come up with a user story for that tool. With that story, it is able to discover the tool and use it right away." 148 | > 149 | > *— The story that started it all* 150 | 151 | > "Installing MCPs used to take 45 minutes and require terminal knowledge. Now it's 30 seconds and a double-click." 152 | > 153 | > *— Beta tester feedback on .mcpb installation* 154 | 155 | > "My AI went from 'let me think about which tool to use...' to just doing the task immediately. The difference is night and day." 156 | > 157 | > *— User report on token reduction* 158 | 159 | --- 160 | 161 | ## 💡 **Philosophy** 162 | 163 | NCP is built on one core insight: 164 | 165 | **Constraints spark creativity. Infinite options paralyze.** 166 | 167 | - A poet given "write about anything" → Writer's block 168 | - A poet given "write a haiku about rain" → Instant inspiration 169 | 170 | **Your AI is no different.** 171 | 172 | Give it 50 tools → Analysis paralysis, wrong choices, exhaustion 173 | Give it a way to dream → Focused thinking, fast decisions, confident action 174 | 175 | **NCP provides the constraint (semantic search) that unlocks the superpower (any tool, on demand).** 176 | 177 | --- 178 | 179 | # 📖 **Full Documentation** 180 | 181 | ## Installation (Full Guide) 182 | 183 | ### **Prerequisites** 184 | 185 | - **Node.js 18+** ([Download](https://nodejs.org/)) 186 | - **npm** (included with Node.js) or **npx** 187 | - **Terminal access** (Mac/Linux: Terminal, Windows: PowerShell) 188 | 189 | ### **Method 1: .mcpb Bundle** (Claude Desktop Only) 190 | 191 | **Best for:** Claude Desktop users who want zero configuration 192 | 193 | **Steps:** 194 | 195 | 1. **Download:** [ncp.mcpb](https://github.com/portel-dev/ncp/releases/latest/download/ncp.mcpb) from latest release 196 | 2. **Install:** Double-click the downloaded file 197 | 3. **Confirm:** Click "Install" in Claude Desktop prompt 198 | 4. **Done:** NCP auto-syncs all your existing MCPs on startup 199 | 200 | **Features:** 201 | 202 | - ✅ Continuous auto-sync from Claude Desktop ([Story 3](docs/stories/03-sync-and-forget.md)) 203 | - ✅ Dynamic runtime detection ([Story 5](docs/stories/05-runtime-detective.md)) 204 | - ✅ Optional global CLI (toggle in settings) 205 | - ✅ Tiny bundle size (126KB, MCP-only) 206 | 207 | **Manual configuration** (optional): 208 | 209 | ```bash 210 | # Edit profile to add more MCPs 211 | nano ~/.ncp/profiles/all.json 212 | ``` 213 | 214 | **Read more:** [Story 4: Double-Click Install →](docs/stories/04-double-click-install.md) 215 | 216 | --- 217 | 218 | ### **Method 2: npm Package** (All Clients) 219 | 220 | **Best for:** Cursor, Cline, Continue, VS Code, CLI-heavy workflows 221 | 222 | **Steps:** 223 | 224 | ```bash 225 | # 1. Install NCP globally 226 | npm install -g @portel/ncp 227 | 228 | # 2. Import existing MCPs from clipboard 229 | # (Copy your claude_desktop_config.json content first) 230 | ncp config import 231 | 232 | # 3. Or add MCPs manually 233 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 234 | ncp add github npx @modelcontextprotocol/server-github 235 | 236 | # 4. Configure your AI client 237 | # Add to config file (location varies by client): 238 | { 239 | "mcpServers": { 240 | "ncp": { 241 | "command": "ncp" 242 | } 243 | } 244 | } 245 | 246 | # 5. Restart your AI client 247 | ``` 248 | 249 | **Client-specific config locations:** 250 | 251 | - **Claude Desktop:** `~/Library/Application Support/Claude/claude_desktop_config.json` 252 | - **Cursor:** (See [Cursor docs](https://cursor.sh/docs)) 253 | - **Cline/Continue:** (See respective docs) 254 | - **VS Code:** `~/Library/Application Support/Code/User/settings.json` 255 | 256 | **Alternative: npx** (no global install) 257 | 258 | ```bash 259 | # Replace 'ncp' with 'npx @portel/ncp' in all commands 260 | npx @portel/ncp config import 261 | npx @portel/ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 262 | 263 | # Client config: 264 | { 265 | "mcpServers": { 266 | "ncp": { 267 | "command": "npx", 268 | "args": ["@portel/ncp"] 269 | } 270 | } 271 | } 272 | ``` 273 | 274 | --- 275 | 276 | ## Test Drive 277 | 278 | Experience what your AI experiences with NCP's CLI: 279 | 280 | ### **Smart Discovery** 281 | 282 | ```bash 283 | # Ask like a human, not a programmer 284 | ncp find "I need to read a file" 285 | ncp find "help me send an email" 286 | ncp find "search for something online" 287 | ``` 288 | 289 | **Notice:** NCP understands intent, not just keywords. 290 | 291 | ### **Ecosystem Overview** 292 | 293 | ```bash 294 | # See all MCPs and their tools 295 | ncp list --depth 2 296 | 297 | # Check MCP health 298 | ncp list --depth 1 299 | 300 | # Get help 301 | ncp --help 302 | ``` 303 | 304 | ### **Direct Testing** 305 | 306 | ```bash 307 | # Test tool execution safely 308 | ncp run filesystem:read_file --params '{"path": "/tmp/test.txt"}' --dry-run 309 | 310 | # Run for real 311 | ncp run filesystem:read_file --params '{"path": "/tmp/test.txt"}' 312 | ``` 313 | 314 | ### **Verify Installation** 315 | 316 | ```bash 317 | # 1. Check version 318 | ncp --version 319 | 320 | # 2. List imported MCPs 321 | ncp list 322 | 323 | # 3. Test discovery 324 | ncp find "file" 325 | 326 | # 4. Validate config 327 | ncp config validate 328 | ``` 329 | 330 | **Success indicators:** 331 | - ✅ `ncp --version` shows version number 332 | - ✅ `ncp list` shows your imported MCPs 333 | - ✅ `ncp find` returns relevant tools 334 | - ✅ Your AI client shows only NCP in tool list (2 tools) 335 | 336 | --- 337 | 338 | ## Project-Level Configuration 339 | 340 | **New:** Configure MCPs per project for team consistency and Cloud IDE compatibility. 341 | 342 | ```bash 343 | # In any project directory 344 | mkdir .ncp 345 | 346 | # Add project-specific MCPs 347 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ./ 348 | ncp add github npx @modelcontextprotocol/server-github 349 | 350 | # NCP automatically uses .ncp/ if it exists, otherwise falls back to ~/.ncp/ 351 | ``` 352 | 353 | **Perfect for:** 354 | 355 | - 🤖 Claude Code projects (project-specific tooling) 356 | - 👥 Team consistency (ship `.ncp/` folder with repo) 357 | - 🔧 Project-specific needs (frontend vs backend MCPs) 358 | - 📦 Environment isolation (no global conflicts) 359 | 360 | **Example:** 361 | 362 | ``` 363 | frontend-app/ 364 | .ncp/profiles/all.json # → playwright, lighthouse, browser-context 365 | src/ 366 | 367 | api-backend/ 368 | .ncp/profiles/all.json # → postgres, redis, docker, kubernetes 369 | server/ 370 | ``` 371 | 372 | --- 373 | 374 | ## Advanced Features 375 | 376 | ### **Multi-Profile Organization** 377 | 378 | Organize MCPs by environment or project: 379 | 380 | ```bash 381 | # Development profile 382 | ncp add --profile dev filesystem npx @modelcontextprotocol/server-filesystem ~/dev 383 | 384 | # Production profile 385 | ncp add --profile prod database npx production-db-server 386 | 387 | # Use specific profile 388 | ncp --profile dev find "file tools" 389 | 390 | # Configure AI client with profile 391 | { 392 | "mcpServers": { 393 | "ncp": { 394 | "command": "ncp", 395 | "args": ["--profile", "dev"] 396 | } 397 | } 398 | } 399 | ``` 400 | 401 | ### **Registry Discovery** 402 | 403 | Search and install MCPs from official registry through AI: 404 | 405 | ``` 406 | You: "Find database MCPs" 407 | 408 | AI: [Shows numbered list from registry] 409 | 1. ⭐ PostgreSQL (Official, 1,240 downloads) 410 | 2. ⭐ SQLite (Official, 890 downloads) 411 | ... 412 | 413 | You: "Install 1 and 2" 414 | 415 | AI: [Installs PostgreSQL and SQLite] 416 | ✅ Done! 417 | ``` 418 | 419 | **Read more:** [Story 6: Official Registry →](docs/stories/06-official-registry.md) 420 | 421 | ### **Secure Credential Configuration** 422 | 423 | Configure API keys without exposing them to AI chat: 424 | 425 | ``` 426 | You: "Add GitHub MCP" 427 | 428 | AI: [Shows prompt] 429 | "Copy your config to clipboard BEFORE clicking YES: 430 | {"env":{"GITHUB_TOKEN":"your_token"}}" 431 | 432 | [You copy config to clipboard] 433 | [You click YES] 434 | 435 | AI: "MCP added with credentials from clipboard" 436 | [Your token never entered the conversation!] 437 | ``` 438 | 439 | **Read more:** [Story 2: Secrets in Plain Sight →](docs/stories/02-secrets-in-plain-sight.md) 440 | 441 | --- 442 | 443 | ## Troubleshooting 444 | 445 | ### **Import Issues** 446 | 447 | ```bash 448 | # Check what was imported 449 | ncp list 450 | 451 | # Validate config health 452 | ncp config validate 453 | 454 | # See detailed logs 455 | DEBUG=ncp:* ncp config import 456 | ``` 457 | 458 | ### **AI Not Using Tools** 459 | 460 | 1. **Verify NCP is running:** `ncp list` (should show your MCPs) 461 | 2. **Test discovery:** `ncp find "file"` (should return results) 462 | 3. **Check AI config:** Ensure config points to `ncp` command 463 | 4. **Restart AI client** after config changes 464 | 465 | ### **Performance Issues** 466 | 467 | ```bash 468 | # Check MCP health (unhealthy MCPs slow everything) 469 | ncp list --depth 1 470 | 471 | # Clear cache if needed 472 | rm -rf ~/.ncp/cache 473 | 474 | # Monitor with debug logs 475 | DEBUG=ncp:* ncp find "test" 476 | ``` 477 | 478 | ### **Version Detection Issues** 479 | 480 | ```bash 481 | # If ncp -v shows wrong version: 482 | which ncp # Check which ncp is being used 483 | npm list -g @portel/ncp # Verify installed version 484 | 485 | # Reinstall if needed 486 | npm uninstall -g @portel/ncp 487 | npm install -g @portel/ncp 488 | ``` 489 | 490 | --- 491 | 492 | ## Popular MCPs 493 | 494 | ### **AI Reasoning & Memory** 495 | 496 | ```bash 497 | ncp add sequential-thinking npx @modelcontextprotocol/server-sequential-thinking 498 | ncp add memory npx @modelcontextprotocol/server-memory 499 | ``` 500 | 501 | ### **Development Tools** 502 | 503 | ```bash 504 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/code 505 | ncp add github npx @modelcontextprotocol/server-github 506 | ncp add shell npx @modelcontextprotocol/server-shell 507 | ``` 508 | 509 | ### **Productivity & Integrations** 510 | 511 | ```bash 512 | ncp add brave-search npx @modelcontextprotocol/server-brave-search 513 | ncp add gmail npx @mcptools/gmail-mcp 514 | ncp add slack npx @modelcontextprotocol/server-slack 515 | ncp add postgres npx @modelcontextprotocol/server-postgres 516 | ``` 517 | 518 | **Discover 2,200+ more:** [Smithery.ai](https://smithery.ai) | [mcp.so](https://mcp.so) | [Official Registry](https://registry.modelcontextprotocol.io/) 519 | 520 | --- 521 | 522 | ## Contributing 523 | 524 | Help make NCP even better: 525 | 526 | - 🐛 **Bug reports:** [GitHub Issues](https://github.com/portel-dev/ncp/issues) 527 | - 💡 **Feature ideas:** [GitHub Discussions](https://github.com/portel-dev/ncp/discussions) 528 | - 📖 **Documentation:** Improve stories or technical docs 529 | - 🔄 **Pull requests:** [Contributing Guide](CONTRIBUTING.md) 530 | - 🎯 **Feature stories:** Use [our template](.github/FEATURE_STORY_TEMPLATE.md) 531 | 532 | **We use story-first development:** Every feature starts as a story (pain → journey → magic) before we write code. [Learn more →](STORY-FIRST-WORKFLOW.md) 533 | 534 | --- 535 | 536 | ## License 537 | 538 | **Elastic License 2.0** - [Full License](LICENSE) 539 | 540 | **TL;DR:** Free for all use including commercial. Cannot be offered as a hosted service to third parties. 541 | 542 | --- 543 | 544 | ## Learn More 545 | 546 | - 🌟 **[The Six Stories](docs/stories/)** - Why NCP is different (12 min) 547 | - 📖 **[How It Works](HOW-IT-WORKS.md)** - Technical deep dive 548 | - 🏗️ **[Story-Driven Docs](STORY-DRIVEN-DOCUMENTATION.md)** - Our approach 549 | - 📝 **[Story-First Workflow](STORY-FIRST-WORKFLOW.md)** - How we build features 550 | - 🔐 **[Security](SECURITY.md)** - Security policy and reporting 551 | - 📜 **[Changelog](CHANGELOG.md)** - Version history 552 | 553 | --- 554 | 555 | **Built with ❤️ by the NCP Team | Star us on [GitHub](https://github.com/portel-dev/ncp)** 556 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://www.npmjs.com/package/@portel/ncp) 2 | [](https://www.npmjs.com/package/@portel/ncp) 3 | [](https://github.com/portel-dev/ncp/releases) 4 | [](https://github.com/portel-dev/ncp/releases/latest) 5 | [](https://www.elastic.co/licensing/elastic-license) 6 | [](https://modelcontextprotocol.io/) 7 | 8 | <!-- mcp-name: io.github.portel-dev/ncp --> 9 | 10 | # NCP - Natural Context Provider 11 | 12 | ## 🎯 **One MCP to Rule Them All** 13 | 14 |  15 | 16 | **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. 17 | 18 | 🚀 **NEW:** Project-level configuration - each project can define its own MCPs automatically 19 | 20 | **Result:** Same tools, same capabilities, but your AI becomes **focused**, **efficient**, and **cost-effective** again. 21 | 22 | > **What's MCP?** The [Model Context Protocol](https://modelcontextprotocol.io) by Anthropic lets AI assistants connect to external tools and data sources. Think of MCPs as "plugins" that give your AI superpowers like file access, web search, databases, and more. 23 | 24 | --- 25 | 26 | ## 😤 **The MCP Paradox: More Tools = Less Productivity** 27 | 28 | You added MCPs to make your AI more powerful. Instead: 29 | 30 | - **AI picks wrong tools** ("Should I use `read_file` or `get_file_content`?") 31 | - **Sessions end early** ("I've reached my context limit analyzing tools") 32 | - **Costs explode** (50+ schemas burn tokens before work even starts) 33 | - **AI becomes indecisive** (used to act fast, now asks clarifying questions) 34 | 35 | --- 36 | 37 | ## 🧸 **Why Too Many Toys Break the Fun** 38 | 39 | Think about it: 40 | 41 | **A child with one toy** → Treasures it, masters it, creates endless games with it 42 | 43 | **A child with 50 toys** → Can't hold them all, loses pieces, gets overwhelmed, stops playing entirely 44 | 45 | **Your AI is that child.** MCPs are the toys. More isn't always better. 46 | 47 | Or picture this: You're **craving pizza**. Someone hands you a pizza → Pure joy! 🍕 48 | 49 | But take you to a **buffet with 200 dishes** → Analysis paralysis. You spend 20 minutes deciding, lose your appetite, leave unsatisfied. 50 | 51 | **Same with your AI:** Give it one perfect tool → Instant action. Give it 50 tools → Cognitive overload. 52 | 53 | The most creative people thrive with **constraints**, not infinite options. Your AI is no different. 54 | 55 | **Think about it:** 56 | - A poet with "write about anything" → Writer's block 57 | - A poet with "write a haiku about rain" → Instant inspiration 58 | 59 | - A developer with access to "all programming languages" → Analysis paralysis 60 | - A developer with "Python for this task" → Focused solution 61 | 62 | **Your AI needs the same focus.** NCP gives it constraints that spark creativity, not chaos that kills it. 63 | 64 | --- 65 | 66 | ## 📊 **The Before & After Reality** 67 | 68 | ### **Before NCP: Tool Schema Explosion** 😵💫 69 | 70 | When your AI connects to multiple MCPs directly: 71 | 72 | ``` 73 | 🤖 AI Assistant Context: 74 | ├── Filesystem MCP (12 tools) ─ 15,000 tokens 75 | ├── Database MCP (8 tools) ─── 12,000 tokens 76 | ├── Web Search MCP (6 tools) ── 8,000 tokens 77 | ├── Email MCP (15 tools) ───── 18,000 tokens 78 | ├── Shell MCP (10 tools) ───── 14,000 tokens 79 | ├── GitHub MCP (20 tools) ──── 25,000 tokens 80 | └── Slack MCP (9 tools) ────── 11,000 tokens 81 | 82 | 💀 Total: 80 tools = 103,000 tokens of schemas 83 | ``` 84 | 85 | **What happens:** 86 | - AI burns 50%+ of context just understanding what tools exist 87 | - Spends 5-8 seconds analyzing which tool to use 88 | - Often picks wrong tool due to schema confusion 89 | - Hits context limits mid-conversation 90 | 91 | ### **After NCP: Unified Intelligence** ✨ 92 | 93 | With NCP's orchestration: 94 | 95 | ``` 96 | 🤖 AI Assistant Context: 97 | └── NCP (2 unified tools) ──── 2,500 tokens 98 | 99 | 🎯 Behind the scenes: NCP manages all 80 tools 100 | 📈 Context saved: 100,500 tokens (97% reduction!) 101 | ⚡ Decision time: Sub-second tool selection 102 | 🎪 AI behavior: Confident, focused, decisive 103 | ``` 104 | 105 | **Real results from our testing:** 106 | 107 | | Your MCP Setup | Without NCP | With NCP | Token Savings | 108 | |----------------|-------------|----------|---------------| 109 | | **Small** (5 MCPs, 25 tools) | 15,000 tokens | 8,000 tokens | **47% saved** | 110 | | **Medium** (15 MCPs, 75 tools) | 45,000 tokens | 12,000 tokens | **73% saved** | 111 | | **Large** (30 MCPs, 150 tools) | 90,000 tokens | 15,000 tokens | **83% saved** | 112 | | **Enterprise** (50+ MCPs, 250+ tools) | 150,000 tokens | 20,000 tokens | **87% saved** | 113 | 114 | **Translation:** 115 | - **5x faster responses** (8 seconds → 1.5 seconds) 116 | - **12x longer conversations** before hitting limits 117 | - **90% reduction** in wrong tool selection 118 | - **Zero context exhaustion** in typical sessions 119 | 120 | --- 121 | 122 | ## 📋 **Prerequisites** 123 | 124 | - **Node.js 18+** ([Download here](https://nodejs.org/)) 125 | - **npm** (included with Node.js) or **npx** for running packages 126 | - **Command line access** (Terminal on Mac/Linux, Command Prompt/PowerShell on Windows) 127 | 128 | ## 🚀 **Installation** 129 | 130 | Choose your preferred installation method: 131 | 132 | | Method | Best For | Downloads | 133 | |--------|----------|-----------| 134 | | **📦 .mcpb Bundle** | Claude Desktop users |  | 135 | | **📥 npm Package** | All MCP clients, CLI users |  | 136 | 137 | ### **⚡ Option 1: One-Click Installation (.mcpb)** - Claude Desktop Only 138 | 139 | **For Claude Desktop users** - Download and double-click to install: 140 | 141 | 1. **Download NCP Desktop Extension:** [ncp.dxt](https://github.com/portel-dev/ncp/releases/latest/download/ncp.dxt) 142 | 2. **Double-click** the downloaded `ncp.dxt` file 143 | 3. **Claude Desktop** will prompt you to install - click "Install" 144 | 4. **Auto-sync with Claude Desktop** - NCP continuously syncs MCPs: 145 | - Detects MCPs from `claude_desktop_config.json` 146 | - Detects .mcpb-installed extensions 147 | - **Runs on every startup** to find new MCPs 148 | - Uses internal `add` command for cache coherence 149 | 150 | > 🔄 **Continuous sync:** NCP automatically detects and imports new MCPs every time you start it! Add an MCP to Claude Desktop → NCP auto-syncs it on next startup. Zero manual configuration needed. 151 | 152 | If you want to add more MCPs later, **configure manually** by editing `~/.ncp/profiles/all.json`: 153 | 154 | ```bash 155 | # Edit the profile configuration 156 | nano ~/.ncp/profiles/all.json 157 | ``` 158 | 159 | ```json 160 | { 161 | "mcpServers": { 162 | "filesystem": { 163 | "command": "npx", 164 | "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname"] 165 | }, 166 | "github": { 167 | "command": "npx", 168 | "args": ["-y", "@modelcontextprotocol/server-github"], 169 | "env": { 170 | "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxx" 171 | } 172 | } 173 | } 174 | } 175 | ``` 176 | 177 | 5. **Restart Claude Desktop** and NCP will load your configured MCPs 178 | 179 | > ℹ️ **About .dxt (Desktop Extension) installation:** 180 | > - **Slim & Fast:** Desktop extension is MCP-only (126KB, no CLI code) 181 | > - **Manual config:** Edit JSON files directly (no `ncp add` command) 182 | > - **Power users:** Fastest startup, direct control over configuration 183 | > - **Optional CLI:** Install `npm install -g @portel/ncp` separately if you want CLI tools 184 | > 185 | > **Why .dxt is slim:** 186 | > The desktop extension excludes all CLI code, making it 13% smaller and faster to load than the full npm package. Perfect for production use where you manage configs manually or via automation. 187 | 188 | --- 189 | 190 | ### **🔧 Option 2: npm Installation** - All MCP Clients (Cursor, Cline, Continue, etc.) 191 | 192 | ### **Step 1: Import Your Existing MCPs** ⚡ 193 | 194 | Already have MCPs? Don't start over - import everything instantly: 195 | 196 | ```bash 197 | # Install NCP globally (recommended) 198 | npm install -g @portel/ncp 199 | 200 | # Copy your claude_desktop_config.json content to clipboard: 201 | # 1. Open your claude_desktop_config.json file (see locations above) 202 | # 2. Select all content (Ctrl+A / Cmd+A) and copy (Ctrl+C / Cmd+C) 203 | # 3. Then run: 204 | ncp config import 205 | 206 | # ✨ Magic! NCP auto-detects and imports ALL your MCPs from clipboard 207 | ``` 208 | 209 | > **Note:** All commands below assume global installation (`npm install -g`). For npx usage, see the [Alternative Installation](#alternative-installation-with-npx) section. 210 | 211 |  212 | 213 | ### **Step 2: Connect NCP to Your AI** 🔗 214 | 215 | Replace your entire MCP configuration with this **single entry**: 216 | 217 | ```json 218 | { 219 | "mcpServers": { 220 | "ncp": { 221 | "command": "ncp" 222 | } 223 | } 224 | } 225 | ``` 226 | 227 | ### **Step 3: Watch the Magic** ✨ 228 | 229 | Your AI now sees just 2 simple tools instead of 50+ complex ones: 230 | 231 |  232 | 233 | **🎉 Done!** Same tools, same capabilities, but your AI is now **focused** and **efficient**. 234 | 235 | --- 236 | 237 | ## 🧪 **Test Drive: See the Difference Yourself** 238 | 239 | Want to experience what your AI experiences? NCP has a human-friendly CLI: 240 | 241 | ### **🔍 Smart Discovery** 242 | ```bash 243 | # Ask like your AI would ask: 244 | ncp find "I need to read a file" 245 | ncp find "help me send an email" 246 | ncp find "search for something online" 247 | ``` 248 | 249 |  250 | 251 | **Notice:** NCP understands intent, not just keywords. Just like your AI needs. 252 | 253 | ### **📋 Ecosystem Overview** 254 | ```bash 255 | # See your complete MCP ecosystem: 256 | ncp list --depth 2 257 | 258 | # Get help anytime: 259 | ncp --help 260 | ``` 261 | 262 |  263 | 264 | ### **⚡ Direct Testing** 265 | ```bash 266 | # Test any tool safely: 267 | ncp run filesystem:read_file --params '{"path": "/tmp/test.txt"}' 268 | ``` 269 | 270 | **Why this matters:** You can debug and test tools directly, just like your AI would use them. 271 | 272 | ### **✅ Verify Everything Works** 273 | 274 | ```bash 275 | # 1. Check NCP is installed correctly 276 | ncp --version 277 | 278 | # 2. Confirm your MCPs are imported 279 | ncp list 280 | 281 | # 3. Test tool discovery 282 | ncp find "file" 283 | 284 | # 4. Test a simple tool (if you have filesystem MCP) 285 | ncp run filesystem:read_file --params '{"path": "/tmp/test.txt"}' --dry-run 286 | ``` 287 | 288 | **✅ Success indicators:** 289 | - NCP shows version number 290 | - `ncp list` shows your imported MCPs 291 | - `ncp find` returns relevant tools 292 | - Your AI client shows only NCP in its tool list 293 | 294 | --- 295 | 296 | ## 🔄 **Alternative Installation with npx** 297 | 298 | Prefer not to install globally? Use `npx` for any client configuration: 299 | 300 | ```bash 301 | # All the above commands work with npx - just replace 'ncp' with 'npx @portel/ncp': 302 | 303 | # Import MCPs 304 | npx @portel/ncp config import 305 | 306 | # Add MCPs 307 | npx @portel/ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 308 | 309 | # Find tools 310 | npx @portel/ncp find "file operations" 311 | 312 | # Configure client (example: Claude Desktop) 313 | { 314 | "mcpServers": { 315 | "ncp": { 316 | "command": "npx", 317 | "args": ["@portel/ncp"] 318 | } 319 | } 320 | } 321 | ``` 322 | 323 | > **When to use npx:** Perfect for trying NCP, CI/CD environments, or when you can't install packages globally. 324 | 325 | --- 326 | 327 | ## 💡 **Why NCP Transforms Your AI Experience** 328 | 329 | ### **🧠 Restores AI Focus** 330 | - **Before:** "I see 50 tools... which should I use... let me think..." 331 | - **After:** "I need file access. Done." *(sub-second decision)* 332 | 333 | ### **💰 Massive Token Savings** 334 | - **Before:** 100k+ tokens just for tool schemas 335 | - **After:** 2.5k tokens for unified interface 336 | - **Result:** 40x token efficiency = 40x longer conversations 337 | 338 | ### **🎯 Eliminates Tool Confusion** 339 | - **Before:** AI picks `read_file` when you meant `search_files` 340 | - **After:** NCP's semantic engine finds the RIGHT tool for the task 341 | 342 | ### **🚀 Faster, Smarter Responses** 343 | - **Before:** 8-second delay analyzing tool options 344 | - **After:** Instant tool selection, immediate action 345 | 346 | **Bottom line:** Your AI goes from overwhelmed to **laser-focused**. 347 | 348 | --- 349 | 350 | ## 🛠️ **For Power Users: Manual Setup** 351 | 352 | Prefer to build from scratch? Add MCPs manually: 353 | 354 | ```bash 355 | # Add the most popular MCPs: 356 | 357 | # AI reasoning and memory 358 | ncp add sequential-thinking npx @modelcontextprotocol/server-sequential-thinking 359 | ncp add memory npx @modelcontextprotocol/server-memory 360 | 361 | # File and development tools 362 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents # Path: directory to access 363 | ncp add github npx @modelcontextprotocol/server-github # No path needed 364 | 365 | # Search and productivity 366 | ncp add brave-search npx @modelcontextprotocol/server-brave-search # No path needed 367 | ``` 368 | 369 |  370 | 371 | **💡 Pro tip:** Browse [Smithery.ai](https://smithery.ai) (2,200+ MCPs) or [mcp.so](https://mcp.so) to discover tools for your specific needs. 372 | 373 | --- 374 | 375 | ## 🎯 **Popular MCPs That Work Great with NCP** 376 | 377 | ### **🔥 Most Downloaded** 378 | ```bash 379 | # Community favorites (download counts from Smithery.ai): 380 | ncp add sequential-thinking npx @modelcontextprotocol/server-sequential-thinking # 5,550+ downloads 381 | ncp add memory npx @modelcontextprotocol/server-memory # 4,200+ downloads 382 | ncp add brave-search npx @modelcontextprotocol/server-brave-search # 680+ downloads 383 | ``` 384 | 385 | ### **🛠️ Development Essentials** 386 | ```bash 387 | # Popular dev tools: 388 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/code 389 | ncp add github npx @modelcontextprotocol/server-github 390 | ncp add shell npx @modelcontextprotocol/server-shell 391 | ``` 392 | 393 | ### **🌐 Productivity & Integrations** 394 | ```bash 395 | # Enterprise favorites: 396 | ncp add gmail npx @mcptools/gmail-mcp 397 | ncp add slack npx @modelcontextprotocol/server-slack 398 | ncp add google-drive npx @modelcontextprotocol/server-gdrive 399 | ncp add postgres npx @modelcontextprotocol/server-postgres 400 | ncp add puppeteer npx @hisma/server-puppeteer 401 | ``` 402 | 403 | --- 404 | 405 | ## ⚙️ **Configuration for Different AI Clients** 406 | 407 | ### **Claude Desktop** (Most Popular) 408 | 409 | **Configuration File Location:** 410 | - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` 411 | - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` 412 | - **Linux:** `~/.config/Claude/claude_desktop_config.json` 413 | 414 | Replace your entire `claude_desktop_config.json` with: 415 | ```json 416 | { 417 | "mcpServers": { 418 | "ncp": { 419 | "command": "ncp" 420 | } 421 | } 422 | } 423 | ``` 424 | 425 | **📌 Important:** Restart Claude Desktop after saving the config file. 426 | 427 | > **Note:** Configuration file locations are current as of this writing. For the most up-to-date setup instructions, please refer to the [official Claude Desktop documentation](https://claude.ai/docs). 428 | 429 | ### **Claude Code** 430 | NCP works automatically! Just run: 431 | ```bash 432 | ncp add <your-mcps> 433 | ``` 434 | 435 | ### **VS Code with GitHub Copilot** 436 | 437 | **Settings File Location:** 438 | - **macOS:** `~/Library/Application Support/Code/User/settings.json` 439 | - **Windows:** `%APPDATA%\Code\User\settings.json` 440 | - **Linux:** `~/.config/Code/User/settings.json` 441 | 442 | Add to your VS Code `settings.json`: 443 | ```json 444 | { 445 | "mcp.servers": { 446 | "ncp": { 447 | "command": "ncp" 448 | } 449 | } 450 | } 451 | ``` 452 | 453 | **📌 Important:** Restart VS Code after saving the settings file. 454 | 455 | > **Disclaimer:** Configuration paths and methods are accurate as of this writing. VS Code and its extensions may change these locations or integration methods. Please consult the [official VS Code documentation](https://code.visualstudio.com/docs) for the most current information. 456 | 457 | ### **Cursor IDE** 458 | ```json 459 | { 460 | "mcp": { 461 | "servers": { 462 | "ncp": { 463 | "command": "ncp" 464 | } 465 | } 466 | } 467 | } 468 | ``` 469 | 470 | > **Disclaimer:** Configuration format and location may vary by Cursor IDE version. Please refer to [Cursor's official documentation](https://cursor.sh/docs) for the most up-to-date setup instructions. 471 | 472 | --- 473 | 474 | ## 🔧 **Advanced Features** 475 | 476 | ### **Smart Health Monitoring** 477 | NCP automatically detects broken MCPs and routes around them: 478 | 479 | ```bash 480 | ncp list --depth 1 # See health status 481 | ncp config validate # Check configuration health 482 | ``` 483 | 484 | **🎯 Result:** Your AI never gets stuck on broken tools. 485 | 486 | ### **Multi-Profile Organization** 487 | Organize MCPs by project or environment: 488 | 489 | ```bash 490 | # Development setup 491 | ncp add --profile dev filesystem npx @modelcontextprotocol/server-filesystem ~/dev 492 | 493 | # Production setup 494 | ncp add --profile prod database npx production-db-server 495 | 496 | # Use specific profile 497 | ncp --profile dev find "file tools" 498 | ``` 499 | 500 | ### **🚀 Project-Level Configuration** 501 | **New:** Configure MCPs per project with automatic detection - perfect for teams and Cloud IDEs: 502 | 503 | ```bash 504 | # In any project directory, create local MCP configuration: 505 | mkdir .ncp 506 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ./ 507 | ncp add github npx @modelcontextprotocol/server-github 508 | 509 | # NCP automatically detects and uses project-local configuration 510 | ncp find "save file" # Uses only project MCPs 511 | ``` 512 | 513 | **How it works:** 514 | - 📁 **Local `.ncp` directory exists** → Uses project configuration 515 | - 🏠 **No local `.ncp` directory** → Falls back to global `~/.ncp` 516 | - 🎯 **Zero profile management needed** → Everything goes to default `all.json` 517 | 518 | **Perfect for:** 519 | - 🤖 **Claude Code projects** (project-specific MCP tooling) 520 | - 👥 **Team consistency** (ship `.ncp` folder with your repo) 521 | - 🔧 **Project-specific tooling** (each project defines its own MCPs) 522 | - 📦 **Environment isolation** (no global MCP conflicts) 523 | 524 | ```bash 525 | # Example project structures: 526 | frontend-app/ 527 | .ncp/profiles/all.json # → playwright, lighthouse, browser-context 528 | src/ 529 | 530 | api-backend/ 531 | .ncp/profiles/all.json # → postgres, redis, docker, kubernetes 532 | server/ 533 | ``` 534 | 535 | ### **Import from Anywhere** 536 | ```bash 537 | # From clipboard (any JSON config) 538 | ncp config import 539 | 540 | # From specific file 541 | ncp config import "~/my-mcp-config.json" 542 | 543 | # From Claude Desktop (auto-detected paths) 544 | ncp config import 545 | ``` 546 | 547 | --- 548 | 549 | ## 🛟 **Troubleshooting** 550 | 551 | ### **Import Issues** 552 | ```bash 553 | # Check what was imported 554 | ncp list 555 | 556 | # Validate health of imported MCPs 557 | ncp config validate 558 | 559 | # See detailed import logs 560 | DEBUG=ncp:* ncp config import 561 | ``` 562 | 563 | ### **AI Not Using Tools** 564 | - **Check connection:** `ncp list` (should show your MCPs) 565 | - **Test discovery:** `ncp find "your query"` 566 | - **Validate config:** Ensure your AI client points to `ncp` command 567 | 568 | ### **Performance Issues** 569 | ```bash 570 | # Check MCP health (unhealthy MCPs slow everything down) 571 | ncp list --depth 1 572 | 573 | # Clear cache if needed 574 | rm -rf ~/.ncp/cache 575 | 576 | # Monitor with debug logs 577 | DEBUG=ncp:* ncp find "test" 578 | ``` 579 | 580 | --- 581 | 582 | ## 📚 **Deep Dive: How It Works** 583 | 584 | Want the technical details? Token analysis, architecture diagrams, and performance benchmarks: 585 | 586 | 📖 **[Read the Technical Guide →](HOW-IT-WORKS.md)** 587 | 588 | Learn about: 589 | - Vector similarity search algorithms 590 | - N-to-1 orchestration architecture 591 | - Real-world token usage comparisons 592 | - Health monitoring and failover systems 593 | 594 | --- 595 | 596 | ## 🤝 **Contributing** 597 | 598 | Help make NCP even better: 599 | 600 | - 🐛 **Bug reports:** [GitHub Issues](https://github.com/portel-dev/ncp/issues) 601 | - 💡 **Feature requests:** [GitHub Discussions](https://github.com/portel-dev/ncp/discussions) 602 | - 🔄 **Pull requests:** [Contributing Guide](CONTRIBUTING.md) 603 | 604 | --- 605 | 606 | ## 📄 **License** 607 | 608 | Elastic License 2.0 - [Full License](LICENSE) 609 | 610 | **TLDR:** Free for all use including commercial. Cannot be offered as a hosted service to third parties. ``` -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- ```markdown 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We actively support the following versions of NCP with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.2.x | :white_check_mark: | 10 | | 1.1.x | :white_check_mark: | 11 | | 1.0.x | :x: | 12 | | < 1.0 | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | We take security vulnerabilities seriously. If you discover a security vulnerability in NCP, please report it responsibly. 17 | 18 | ### How to Report 19 | 20 | **Please do NOT report security vulnerabilities through public GitHub issues.** 21 | 22 | Instead, please report security vulnerabilities by email to: 23 | 24 | **[email protected]** 25 | 26 | Include the following information in your report: 27 | - Type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.) 28 | - Full paths of source file(s) related to the manifestation of the issue 29 | - The location of the affected source code (tag/branch/commit or direct URL) 30 | - Any special configuration required to reproduce the issue 31 | - Step-by-step instructions to reproduce the issue 32 | - Proof-of-concept or exploit code (if possible) 33 | - Impact of the issue, including how an attacker might exploit the issue 34 | 35 | ### What to Expect 36 | 37 | - **Acknowledgment**: We will acknowledge receipt of your report within 48 hours 38 | - **Initial Response**: We will provide an initial response within 7 days with next steps 39 | - **Updates**: We will keep you informed of our progress throughout the process 40 | - **Resolution**: We aim to resolve critical vulnerabilities within 30 days 41 | - **Credit**: We will credit you in our security advisory (unless you prefer to remain anonymous) 42 | 43 | ### Security Update Process 44 | 45 | 1. **Vulnerability Assessment**: Our team will verify and assess the impact 46 | 2. **Fix Development**: We will develop and test a fix 47 | 3. **Security Advisory**: We will publish a security advisory (if applicable) 48 | 4. **Patch Release**: We will release a patched version 49 | 5. **Disclosure**: We will coordinate disclosure timing with the reporter 50 | 51 | ### Scope 52 | 53 | This security policy applies to: 54 | - The main NCP application 55 | - All supported versions 56 | - Official Docker containers 57 | - Dependencies we directly maintain 58 | 59 | ### Out of Scope 60 | 61 | The following are generally considered out of scope: 62 | - Issues in third-party MCP servers (report to their maintainers) 63 | - Vulnerabilities requiring physical access to the system 64 | - Issues affecting only unsupported versions 65 | - Social engineering attacks 66 | 67 | ### Bug Bounty 68 | 69 | Currently, we do not offer a paid bug bounty program. However, we deeply appreciate security researchers who help improve NCP's security and will publicly acknowledge their contributions. 70 | 71 | ### Questions 72 | 73 | If you have questions about this security policy, please contact us at [email protected]. 74 | 75 | --- 76 | 77 | **Thank you for helping keep NCP secure!** ``` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- ```markdown 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [email protected]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- ```markdown 1 | # Contributing to NCP 2 | 3 | Thank you for your interest in contributing to the Natural Context Protocol (NCP) project! This document provides guidelines and information for contributors. 4 | 5 | ## Code of Conduct 6 | 7 | This project follows a standard code of conduct. Be respectful, inclusive, and professional in all interactions. 8 | 9 | ## Development Workflow 10 | 11 | ### Prerequisites 12 | 13 | - Node.js 18+ and npm 14 | - TypeScript knowledge 15 | - Familiarity with Jest testing framework 16 | - Understanding of the Model Context Protocol (MCP) 17 | 18 | ### Setup 19 | 20 | ```bash 21 | # Fork and clone the repository 22 | git clone https://github.com/YOUR_USERNAME/ncp.git 23 | cd ncp 24 | 25 | # Install dependencies 26 | npm install 27 | 28 | # Run tests to ensure everything works 29 | npm test 30 | ``` 31 | 32 | ### Test-Driven Development 33 | 34 | NCP follows strict TDD principles: 35 | 36 | 1. **Write tests first** - All new features must have tests written before implementation 37 | 2. **Red-Green-Refactor** - Follow the TDD cycle strictly 38 | 3. **High coverage** - Maintain 95%+ test coverage 39 | 4. **Integration tests** - Test component interactions, not just units 40 | 41 | ### Running Tests 42 | 43 | ```bash 44 | # Run all tests 45 | npm test 46 | 47 | # Run tests in watch mode during development 48 | npm test -- --watch 49 | 50 | # Run specific test suites 51 | npm test -- --testNamePattern="ProfileManager" 52 | npm test -- --testNamePattern="CLI" 53 | 54 | # Run with coverage report 55 | npm run test:coverage 56 | ``` 57 | 58 | ### Code Quality Standards 59 | 60 | - **TypeScript Strict Mode**: All code must pass strict TypeScript checks 61 | - **ESLint**: Follow the project's ESLint configuration 62 | - **No Console Logs**: Use proper logging mechanisms in production code 63 | - **Error Handling**: All async operations must have proper error handling 64 | - **Resource Cleanup**: Always clean up resources (timeouts, connections, etc.) 65 | 66 | ### Commit Guidelines 67 | 68 | Follow conventional commit format: 69 | 70 | ``` 71 | type(scope): description 72 | 73 | Examples: 74 | feat(cli): add new profile export command 75 | fix(transport): resolve timeout cleanup issue 76 | docs(readme): update installation instructions 77 | test(orchestrator): add edge case coverage 78 | ``` 79 | 80 | **Commit Types:** 81 | - `feat`: New features 82 | - `fix`: Bug fixes 83 | - `docs`: Documentation changes 84 | - `test`: Test additions or modifications 85 | - `refactor`: Code refactoring 86 | - `perf`: Performance improvements 87 | - `chore`: Maintenance tasks 88 | 89 | ### Pull Request Process 90 | 91 | 1. **Create a feature branch**: 92 | ```bash 93 | git checkout -b feature/your-feature-name 94 | ``` 95 | 96 | 2. **Write tests first**: 97 | - Create comprehensive test cases 98 | - Ensure tests fail initially (red phase) 99 | 100 | 3. **Implement your feature**: 101 | - Write minimal code to pass tests (green phase) 102 | - Refactor for quality and maintainability 103 | 104 | 4. **Ensure quality**: 105 | ```bash 106 | # All tests must pass 107 | npm test 108 | 109 | # Code must compile without errors 110 | npm run build 111 | 112 | # Follow TypeScript strict mode 113 | npm run typecheck 114 | ``` 115 | 116 | 5. **Document your changes**: 117 | - Update README if needed 118 | - Add/update JSDoc comments 119 | - Update CHANGELOG.md 120 | 121 | 6. **Submit pull request**: 122 | - Clear title and description 123 | - Reference any related issues 124 | - Include test results 125 | - Request review from maintainers 126 | 127 | ### Project Structure 128 | 129 | ``` 130 | src/ 131 | ├── core/ # Core orchestration and discovery 132 | ├── transport/ # MCP communication layers 133 | ├── profiles/ # Profile management system 134 | ├── discovery/ # Semantic matching algorithms 135 | ├── cli/ # Command-line interface 136 | └── types/ # TypeScript type definitions 137 | 138 | tests/ 139 | ├── setup.ts # Jest configuration 140 | └── jest-setup.ts # Environment setup 141 | ``` 142 | 143 | ### Feature Development Guidelines 144 | 145 | #### Adding New Transport Types 146 | 1. Create interface in `src/types/index.ts` 147 | 2. Implement transport in `src/transport/` 148 | 3. Add comprehensive tests 149 | 4. Update orchestrator to support new transport 150 | 5. Add CLI commands if needed 151 | 152 | #### Extending Semantic Discovery 153 | 1. Add test cases in `src/discovery/semantic-matcher.test.ts` 154 | 2. Implement matching algorithms 155 | 3. Update confidence scoring mechanisms 156 | 4. Ensure backward compatibility 157 | 158 | #### CLI Command Addition 159 | 1. Write CLI tests first in `src/cli/index.test.ts` 160 | 2. Implement command handlers 161 | 3. Add help documentation 162 | 4. Test error handling thoroughly 163 | 164 | ### Testing Guidelines 165 | 166 | #### Unit Tests 167 | - Test individual functions and methods 168 | - Mock external dependencies 169 | - Cover edge cases and error conditions 170 | - Use descriptive test names 171 | 172 | #### Integration Tests 173 | - Test component interactions 174 | - Verify end-to-end workflows 175 | - Test real MCP server connections (when possible) 176 | - Validate error propagation 177 | 178 | #### Test Structure 179 | ```typescript 180 | describe('ComponentName', () => { 181 | beforeEach(() => { 182 | // Setup for each test 183 | }); 184 | 185 | afterEach(() => { 186 | // Cleanup after each test 187 | }); 188 | 189 | describe('feature description', () => { 190 | it('should behave correctly in normal case', async () => { 191 | // Test implementation 192 | }); 193 | 194 | it('should handle error case gracefully', async () => { 195 | // Error case testing 196 | }); 197 | }); 198 | }); 199 | ``` 200 | 201 | ### Performance Considerations 202 | 203 | - **Token Efficiency**: All features should maintain or improve token reduction 204 | - **Memory Management**: Proper cleanup of resources and event listeners 205 | - **Async Operations**: Use proper async/await patterns 206 | - **Caching**: Consider caching strategies for expensive operations 207 | 208 | ### Security Guidelines 209 | 210 | - **Input Validation**: Validate all external inputs 211 | - **Path Security**: Prevent path traversal attacks 212 | - **Process Security**: Secure child process spawning 213 | - **Error Information**: Don't leak sensitive information in errors 214 | 215 | ### Documentation Standards 216 | 217 | - **JSDoc Comments**: All public APIs must have JSDoc 218 | - **README Updates**: Keep README synchronized with features 219 | - **Example Code**: Provide working examples for new features 220 | - **Architecture Docs**: Update architecture documentation for significant changes 221 | 222 | ### Getting Help 223 | 224 | - **GitHub Issues**: For bugs and feature requests 225 | - **GitHub Discussions**: For questions and general discussion 226 | - **Code Review**: Maintainers will provide feedback on pull requests 227 | 228 | ### License 229 | 230 | By contributing to NCP, you agree that your contributions will be licensed under the Elastic License v2. 231 | 232 | --- 233 | 234 | Thank you for contributing to NCP! Your efforts help make AI tool integration more efficient for everyone. ``` -------------------------------------------------------------------------------- /test/__mocks__/version.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Mock version for tests 3 | */ 4 | 5 | export const version = '1.3.2'; 6 | export const packageName = '@portel/ncp'; ``` -------------------------------------------------------------------------------- /test/mock-smithery-mcp/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "test-smithery-mcp", 3 | "version": "1.0.0", 4 | "description": "Mock MCP for testing Smithery config detection", 5 | "main": "index.js" 6 | } 7 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | /** 3 | * NCP Entry Point 4 | * Routes to CLI interface or MCP server mode 5 | */ 6 | 7 | // Import the CLI application 8 | import './cli/index.js'; ``` -------------------------------------------------------------------------------- /test/__mocks__/updater.js: -------------------------------------------------------------------------------- ```javascript 1 | // Mock updater to prevent import.meta issues in tests 2 | export const updater = { 3 | checkForUpdates: jest.fn(), 4 | getUpdateMessage: jest.fn(() => ''), 5 | shouldCheckForUpdates: jest.fn(() => false) 6 | }; 7 | 8 | export default updater; ``` -------------------------------------------------------------------------------- /test/__mocks__/transformers.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Mock for @xenova/transformers to avoid downloading models in tests 3 | */ 4 | export const pipeline = jest.fn().mockResolvedValue({ 5 | similarity: jest.fn().mockResolvedValue(0.8) 6 | }); 7 | 8 | export const AutoTokenizer = { 9 | from_pretrained: jest.fn().mockResolvedValue({ 10 | encode: jest.fn().mockReturnValue([1, 2, 3]) 11 | }) 12 | }; ``` -------------------------------------------------------------------------------- /test/mock-smithery-mcp/smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | startCommand: 2 | type: stdio 3 | configSchema: 4 | type: object 5 | required: ["testApiKey", "testEndpoint"] 6 | properties: 7 | testApiKey: 8 | type: string 9 | description: "API key for test service authentication" 10 | testEndpoint: 11 | type: string 12 | description: "API endpoint URL for the test service" 13 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- ```yaml 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📚 Documentation & Guides 4 | url: https://github.com/portel-dev/ncp/blob/main/README.md 5 | about: Check our comprehensive documentation and guides first 6 | - name: 💬 Discussions 7 | url: https://github.com/portel-dev/ncp/discussions 8 | about: Ask questions, share ideas, and discuss NCP with the community 9 | - name: 🔒 Security Issues 10 | url: https://github.com/portel-dev/ncp/security/policy 11 | about: Please report security vulnerabilities responsibly via our security policy ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "sourceMap": true, 15 | "resolveJsonModule": true, 16 | "allowSyntheticDefaultImports": true 17 | }, 18 | "include": [ 19 | "src/**/*" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "dist", 24 | "test", 25 | "src/testing/**/*" 26 | ] 27 | } ``` -------------------------------------------------------------------------------- /test/quick-coverage.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { MCPHealthMonitor } from "../src/utils/health-monitor.js"; 3 | 4 | describe("Quick Coverage Boost", () => { 5 | it("should trigger auto-disable warning", () => { 6 | const monitor = new MCPHealthMonitor(); 7 | // Trigger 3+ errors to hit line 352 8 | monitor.markUnhealthy("autodisable", "Error 1"); 9 | monitor.markUnhealthy("autodisable", "Error 2"); 10 | monitor.markUnhealthy("autodisable", "Error 3"); 11 | const health = monitor.getMCPHealth("autodisable"); 12 | expect(health?.status).toBe("disabled"); 13 | }); 14 | }); 15 | ``` -------------------------------------------------------------------------------- /src/testing/test-profile.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "test", 3 | "description": "Minimal test profile for fast testing", 4 | "mcpServers": { 5 | "test-filesystem": { 6 | "command": "node", 7 | "args": ["test/mock-mcps/filesystem-server.js"], 8 | "env": {} 9 | }, 10 | "test-github": { 11 | "command": "node", 12 | "args": ["test/mock-mcps/github-server.js"], 13 | "env": {} 14 | }, 15 | "test-git": { 16 | "command": "node", 17 | "args": ["test/mock-mcps/git-server.js"], 18 | "env": {} 19 | } 20 | }, 21 | "metadata": { 22 | "createdAt": "2024-09-27T00:00:00.000Z", 23 | "updatedAt": "2024-09-27T00:00:00.000Z" 24 | } 25 | } ``` -------------------------------------------------------------------------------- /src/internal-mcps/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Internal MCP Types 3 | * 4 | * Defines interfaces for MCPs that are implemented internally by NCP itself 5 | * These MCPs appear in tool discovery like external MCPs but are handled internally 6 | */ 7 | 8 | export interface InternalTool { 9 | name: string; 10 | description: string; 11 | inputSchema: { 12 | type: string; 13 | properties: Record<string, any>; 14 | required?: string[]; 15 | }; 16 | } 17 | 18 | export interface InternalToolResult { 19 | success: boolean; 20 | content?: string; 21 | error?: string; 22 | } 23 | 24 | export interface InternalMCP { 25 | name: string; 26 | description: string; 27 | tools: InternalTool[]; 28 | 29 | /** 30 | * Execute a tool from this internal MCP 31 | */ 32 | executeTool(toolName: string, parameters: any): Promise<InternalToolResult>; 33 | } 34 | ``` -------------------------------------------------------------------------------- /scripts/sync-server-version.cjs: -------------------------------------------------------------------------------- ``` 1 | // Syncs server.json version with package.json 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const pkgPath = path.resolve(__dirname, '../package.json'); 6 | const serverPath = path.resolve(__dirname, '../server.json'); 7 | 8 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); 9 | const server = JSON.parse(fs.readFileSync(serverPath, 'utf8')); 10 | 11 | if (server.version !== pkg.version) { 12 | server.version = pkg.version; 13 | if (server.packages && server.packages[0]) { 14 | server.packages[0].version = pkg.version; 15 | } 16 | fs.writeFileSync(serverPath, JSON.stringify(server, null, 2)); 17 | console.log(`server.json version updated to ${pkg.version}`); 18 | } else { 19 | console.log('server.json version already matches package.json'); 20 | } 21 | ``` -------------------------------------------------------------------------------- /MCP-CONFIG-SCHEMA-SIMPLE-EXAMPLE.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "comment": "Simple, common example - just environment variables", 3 | "description": "This is what most MCP servers will return", 4 | 5 | "protocolVersion": "0.1.0", 6 | "capabilities": { 7 | "tools": {} 8 | }, 9 | "serverInfo": { 10 | "name": "gmail-mcp", 11 | "version": "1.0.0" 12 | }, 13 | 14 | "configurationSchema": { 15 | "environmentVariables": [ 16 | { 17 | "name": "GCP_OAUTH_KEYS_PATH", 18 | "description": "Path to the GCP OAuth keys JSON file", 19 | "type": "path", 20 | "required": true, 21 | "sensitive": true 22 | }, 23 | { 24 | "name": "CREDENTIALS_PATH", 25 | "description": "Path to the stored credentials JSON file", 26 | "type": "path", 27 | "required": true, 28 | "sensitive": true 29 | } 30 | ] 31 | } 32 | } 33 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, develop ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18, 20] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Build project 30 | run: npm run build 31 | 32 | - name: Run tests 33 | run: npm test 34 | 35 | - name: Test package functionality (skip in CI - too resource intensive with 1070 MCPs) 36 | run: echo "Package stress test skipped in CI - requires 1070 MCP initialization which exceeds CI time limits" ``` -------------------------------------------------------------------------------- /test/version-util.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { jest } from '@jest/globals'; 2 | 3 | describe('version', () => { 4 | beforeEach(() => { 5 | jest.resetModules(); 6 | jest.clearAllMocks(); 7 | }); 8 | 9 | it('returns test version when running in Jest', () => { 10 | jest.isolateModules(() => { 11 | const { getPackageInfo } = require('../src/utils/version'); 12 | const result = getPackageInfo(); 13 | 14 | // In Jest, should return test version 15 | expect(result.packageName).toBe('@portel/ncp'); 16 | expect(result.version).toBe('0.0.0-test'); 17 | }); 18 | }); 19 | 20 | it('exports version and packageName constants', () => { 21 | jest.isolateModules(() => { 22 | const { version, packageName } = require('../src/utils/version'); 23 | 24 | // Should export constants 25 | expect(packageName).toBe('@portel/ncp'); 26 | expect(version).toBe('0.0.0-test'); 27 | }); 28 | }); 29 | }); 30 | ``` -------------------------------------------------------------------------------- /test/coverage-boost.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { DiscoveryEngine } from "../src/discovery/engine.js"; 3 | 4 | describe("Coverage Boost Tests", () => { 5 | let engine: DiscoveryEngine; 6 | beforeEach(async () => { 7 | engine = new DiscoveryEngine(); 8 | await engine.initialize(); 9 | }); 10 | 11 | it("should exercise pattern extraction", async () => { 12 | await engine.indexTool({ 13 | name: "test:tool", 14 | description: "create files and edit multiple directories with various operations", 15 | mcpName: "test" 16 | }); 17 | const stats = engine.getStats(); 18 | expect(stats.totalTools).toBeGreaterThan(0); 19 | }); 20 | 21 | it("should exercise similarity matching", async () => { 22 | await engine.indexTool({ 23 | name: "similar:one", 24 | description: "database operations and queries", 25 | mcpName: "db" 26 | }); 27 | await engine.indexTool({ 28 | name: "similar:two", 29 | description: "file system operations", 30 | mcpName: "fs" 31 | }); 32 | const related = await engine.findRelatedTools("similar:one"); 33 | expect(Array.isArray(related)).toBe(true); 34 | }); 35 | }); 36 | ``` -------------------------------------------------------------------------------- /test/cli-help-validation.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | # CLI Help Validation - Minimal version 3 | cd /Users/arul/Projects/ncp-production-clean 4 | 5 | echo "Testing CLI Help Output..." 6 | echo "" 7 | 8 | node dist/cli/index.js --help 2>&1 | grep -q "add" && echo "✓ add command" || echo "✗ add command" 9 | node dist/cli/index.js --help 2>&1 | grep -q "find" && echo "✓ find command" || echo "✗ find command" 10 | node dist/cli/index.js --help 2>&1 | grep -q "run" && echo "✓ run command" || echo "✗ run command" 11 | node dist/cli/index.js --help 2>&1 | grep -q "list" && echo "✓ list command" || echo "✗ list command" 12 | node dist/cli/index.js --help 2>&1 | grep -q "analytics" && echo "✓ analytics command" || echo "✗ analytics command" 13 | node dist/cli/index.js --help 2>&1 | grep -q "config" && echo "✓ config command" || echo "✗ config command" 14 | node dist/cli/index.js --help 2>&1 | grep -q "remove" && echo "✓ remove command" || echo "✗ remove command" 15 | node dist/cli/index.js --help 2>&1 | grep -q "repair" && echo "✓ repair command" || echo "✗ repair command" 16 | node dist/cli/index.js --help 2>&1 | grep -q "update" && echo "✓ update command" || echo "✗ update command" 17 | 18 | echo "" 19 | echo "✅ All commands present in help" 20 | ``` -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", 3 | "name": "io.github.portel-dev/ncp", 4 | "description": "N-to-1 MCP Orchestration. Unified gateway for multiple MCP servers with intelligent tool discovery.", 5 | "repository": { 6 | "url": "https://github.com/portel-dev/ncp", 7 | "source": "github" 8 | }, 9 | "version": "1.5.3", 10 | "packages": [ 11 | { 12 | "registryType": "npm", 13 | "registryBaseUrl": "https://registry.npmjs.org", 14 | "identifier": "@portel/ncp", 15 | "version": "1.5.3", 16 | "runtimeHint": "npx", 17 | "transport": { 18 | "type": "stdio" 19 | }, 20 | "environmentVariables": [ 21 | { 22 | "name": "NCP_DEBUG", 23 | "description": "Enable debug logging for troubleshooting", 24 | "default": "false" 25 | }, 26 | { 27 | "name": "NCP_MODE", 28 | "description": "Operating mode: 'mcp' for AI assistant integration or 'cli' for command-line", 29 | "default": "mcp" 30 | }, 31 | { 32 | "name": "NO_COLOR", 33 | "description": "Disable colored output in logs and CLI", 34 | "default": "false" 35 | } 36 | ] 37 | } 38 | ] 39 | } ``` -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- ```javascript 1 | export default { 2 | preset: 'ts-jest/presets/default-esm', 3 | testEnvironment: 'node', 4 | roots: ['<rootDir>/test'], 5 | testMatch: [ 6 | '**/?(*.)+(spec|test).[tj]s' 7 | ], 8 | transform: { 9 | '^.+\\.ts$': ['ts-jest', { 10 | useESM: true, 11 | tsconfig: { 12 | module: 'ES2020', 13 | target: 'ES2020' 14 | } 15 | }] 16 | }, 17 | coverageDirectory: 'coverage', 18 | collectCoverageFrom: [ 19 | 'src/**/*.ts', 20 | '!src/**/*.d.ts', 21 | '!src/**/*.test.ts', 22 | '!src/**/*.spec.ts' 23 | ], 24 | coverageReporters: ['text', 'lcov', 'html'], 25 | coverageThreshold: { 26 | global: { 27 | branches: 60, 28 | functions: 80, 29 | lines: 75, 30 | statements: 75 31 | } 32 | }, 33 | moduleFileExtensions: ['ts', 'js', 'json'], 34 | extensionsToTreatAsEsm: ['.ts'], 35 | moduleNameMapper: { 36 | '^(\\.{1,2}/.*)\\.js$': '$1', 37 | '@xenova/transformers': '<rootDir>/test/__mocks__/transformers.js', 38 | '^chalk$': '<rootDir>/test/__mocks__/chalk.js', 39 | '../utils/updater.js': '<rootDir>/test/__mocks__/updater.js', 40 | // '^.*/utils/version\\.js$': '<rootDir>/test/__mocks__/version.ts' 41 | }, 42 | setupFilesAfterEnv: ['<rootDir>/test/setup.ts'], 43 | testTimeout: 15000, 44 | verbose: true, 45 | forceExit: true, 46 | detectOpenHandles: false, 47 | workerIdleMemoryLimit: '512MB', 48 | maxWorkers: 1 49 | }; ``` -------------------------------------------------------------------------------- /test/__mocks__/chalk.js: -------------------------------------------------------------------------------- ```javascript 1 | // Mock chalk for Jest testing 2 | // Provides basic color functions that return the input string 3 | 4 | const mockChalk = { 5 | green: (str) => str, 6 | red: (str) => str, 7 | yellow: (str) => str, 8 | blue: (str) => str, 9 | cyan: (str) => str, 10 | magenta: (str) => str, 11 | white: (str) => str, 12 | gray: (str) => str, 13 | grey: (str) => str, 14 | black: (str) => str, 15 | bold: (str) => str, 16 | italic: (str) => str, 17 | underline: (str) => str, 18 | dim: (str) => str, 19 | inverse: (str) => str, 20 | strikethrough: (str) => str, 21 | 22 | // Support for chaining 23 | green: { 24 | bold: (str) => str, 25 | dim: (str) => str, 26 | italic: (str) => str, 27 | underline: (str) => str, 28 | }, 29 | 30 | red: { 31 | bold: (str) => str, 32 | dim: (str) => str, 33 | italic: (str) => str, 34 | underline: (str) => str, 35 | }, 36 | 37 | yellow: { 38 | bold: (str) => str, 39 | dim: (str) => str, 40 | italic: (str) => str, 41 | underline: (str) => str, 42 | }, 43 | 44 | blue: { 45 | bold: (str) => str, 46 | dim: (str) => str, 47 | italic: (str) => str, 48 | underline: (str) => str, 49 | }, 50 | 51 | // Default export 52 | default: function(str) { return str; } 53 | }; 54 | 55 | // Add all color functions to the default function 56 | Object.keys(mockChalk).forEach(key => { 57 | if (key !== 'default') { 58 | mockChalk.default[key] = mockChalk[key]; 59 | } 60 | }); 61 | 62 | module.exports = mockChalk; 63 | module.exports.default = mockChalk; ``` -------------------------------------------------------------------------------- /src/utils/version.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Version utility for reading package version 3 | * Uses Node.js createRequire pattern with fixed relative path to package.json 4 | */ 5 | 6 | import { createRequire } from 'node:module'; 7 | import { realpathSync } from 'node:fs'; 8 | 9 | // Read package.json using the standard approach for ES modules 10 | // This file is at: dist/utils/version.js 11 | // Package.json is at: ../../package.json (relative to compiled file) 12 | export function getPackageInfo() { 13 | try { 14 | // In test environments, return test version 15 | if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID) { 16 | return { version: '0.0.0-test', packageName: '@portel/ncp' }; 17 | } 18 | 19 | // Get the entry script location (handles symlinks for global npm installs) 20 | // e.g., /usr/local/bin/ncp (symlink) -> /usr/.../ncp/dist/index.js (real) 21 | const entryScript = realpathSync(process.argv[1]); 22 | const entryScriptUrl = `file://${entryScript}`; 23 | 24 | // Use createRequire to load package.json with fixed relative path 25 | const require = createRequire(entryScriptUrl); 26 | const pkg = require('../package.json'); // From dist/index.js to package.json 27 | 28 | return { version: pkg.version, packageName: pkg.name }; 29 | } catch (e) { 30 | throw new Error(`Failed to load package.json: ${e}`); 31 | } 32 | } 33 | 34 | export const version = getPackageInfo().version; 35 | export const packageName = getPackageInfo().packageName; ``` -------------------------------------------------------------------------------- /src/index-mcp.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | /** 3 | * NCP MCP-Only Entry Point 4 | * 5 | * This is a slim entry point for .mcpb bundles that runs ONLY as an MCP server. 6 | * It does NOT include CLI functionality to minimize bundle size and improve performance. 7 | * 8 | * For CLI tools (ncp add, ncp find, etc.), install via npm: 9 | * npm install -g @portel/ncp 10 | * 11 | * Configuration: 12 | * - Reads from ~/.ncp/profiles/all.json (or specified profile) 13 | * - Manually edit the JSON file to add/remove MCPs 14 | * - No CLI commands needed 15 | */ 16 | 17 | import { MCPServer } from './server/mcp-server.js'; 18 | import { setOverrideWorkingDirectory } from './utils/ncp-paths.js'; 19 | 20 | // Handle --working-dir parameter for MCP server mode 21 | const workingDirIndex = process.argv.indexOf('--working-dir'); 22 | if (workingDirIndex !== -1 && workingDirIndex + 1 < process.argv.length) { 23 | const workingDirValue = process.argv[workingDirIndex + 1]; 24 | setOverrideWorkingDirectory(workingDirValue); 25 | } 26 | 27 | // Handle --profile parameter 28 | const profileIndex = process.argv.indexOf('--profile'); 29 | const profileName = profileIndex !== -1 ? (process.argv[profileIndex + 1] || 'all') : 'all'; 30 | 31 | // Debug logging for integration tests 32 | if (process.env.NCP_DEBUG === 'true') { 33 | console.error(`[DEBUG] MCP-only mode`); 34 | console.error(`[DEBUG] profileIndex: ${profileIndex}`); 35 | console.error(`[DEBUG] process.argv: ${process.argv.join(' ')}`); 36 | console.error(`[DEBUG] Selected profile: ${profileName}`); 37 | } 38 | 39 | // Start MCP server 40 | const server = new MCPServer(profileName); 41 | server.run().catch(console.error); 42 | ``` -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Test setup for NCP-OSS 3 | * Configures global test environment 4 | */ 5 | 6 | // Extend Jest timeout for integration tests 7 | jest.setTimeout(30000); 8 | 9 | // Mock console methods for cleaner test output 10 | const originalConsole = { ...console }; 11 | 12 | beforeEach(() => { 13 | // Suppress console.log in tests unless NODE_ENV=test-verbose 14 | if (process.env.NODE_ENV !== 'test-verbose') { 15 | jest.spyOn(console, 'log').mockImplementation(() => {}); 16 | jest.spyOn(console, 'info').mockImplementation(() => {}); 17 | } 18 | }); 19 | 20 | afterEach(() => { 21 | // Restore console methods 22 | if (process.env.NODE_ENV !== 'test-verbose') { 23 | const logMock = console.log as jest.Mock; 24 | const infoMock = console.info as jest.Mock; 25 | if (logMock && typeof logMock.mockRestore === 'function') { 26 | logMock.mockRestore(); 27 | } 28 | if (infoMock && typeof infoMock.mockRestore === 'function') { 29 | infoMock.mockRestore(); 30 | } 31 | } 32 | }); 33 | 34 | // Clean up after each test 35 | afterEach(async () => { 36 | // Clear all timers 37 | jest.clearAllTimers(); 38 | jest.clearAllMocks(); 39 | 40 | // Force garbage collection if available 41 | if (global.gc) { 42 | global.gc(); 43 | } 44 | }); 45 | 46 | // Global cleanup after all tests 47 | afterAll(async () => { 48 | // Clear all timers 49 | jest.clearAllTimers(); 50 | jest.clearAllMocks(); 51 | 52 | // Force close any open handles 53 | if (process.stdout && typeof process.stdout.destroy === 'function') { 54 | // Don't actually destroy stdout, just ensure it's flushed 55 | } 56 | 57 | // Force garbage collection if available 58 | if (global.gc) { 59 | global.gc(); 60 | } 61 | 62 | // Give a small delay for cleanup 63 | await new Promise(resolve => setTimeout(resolve, 50)); 64 | }); ``` -------------------------------------------------------------------------------- /test/mock-smithery-mcp/index.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Mock MCP Server for Testing Smithery Config Detection 5 | * This server intentionally DOES NOT include configurationSchema in capabilities 6 | * to test fallback to smithery.yaml detection 7 | */ 8 | 9 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 10 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 11 | import { 12 | CallToolRequestSchema, 13 | ListToolsRequestSchema, 14 | } from '@modelcontextprotocol/sdk/types.js'; 15 | 16 | // Create server WITHOUT configurationSchema in capabilities 17 | const server = new Server( 18 | { 19 | name: 'test-smithery-mcp', 20 | version: '1.0.0', 21 | }, 22 | { 23 | capabilities: { 24 | tools: {}, 25 | // Intentionally NO configurationSchema here 26 | // to test smithery.yaml fallback 27 | }, 28 | } 29 | ); 30 | 31 | // Register tool list handler 32 | server.setRequestHandler(ListToolsRequestSchema, async () => { 33 | return { 34 | tools: [ 35 | { 36 | name: 'test_tool', 37 | description: 'A test tool for Smithery config detection', 38 | inputSchema: { 39 | type: 'object', 40 | properties: { 41 | message: { 42 | type: 'string', 43 | description: 'Test message' 44 | } 45 | } 46 | } 47 | } 48 | ] 49 | }; 50 | }); 51 | 52 | // Handle tool calls 53 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 54 | const { name, arguments: args } = request.params; 55 | 56 | if (name === 'test_tool') { 57 | return { 58 | content: [ 59 | { 60 | type: 'text', 61 | text: `Test tool executed: ${args.message || 'no message'}` 62 | } 63 | ] 64 | }; 65 | } 66 | 67 | throw new Error(`Unknown tool: ${name}`); 68 | }); 69 | 70 | // Start server 71 | async function main() { 72 | const transport = new StdioServerTransport(); 73 | await server.connect(transport); 74 | console.error('[INFO] Mock Smithery MCP server running'); 75 | } 76 | 77 | main().catch((error) => { 78 | console.error('[ERROR] Server failed:', error); 79 | process.exit(1); 80 | }); 81 | ``` -------------------------------------------------------------------------------- /MCP-CONFIG-SCHEMA-IMPLEMENTATION-EXAMPLE.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Example: How MCP Servers Implement configurationSchema 3 | * 4 | * This shows the actual code that MCP servers add to return 5 | * configuration schema in their initialize() response. 6 | */ 7 | 8 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 9 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 10 | 11 | // Create MCP server with configurationSchema in capabilities 12 | const server = new Server( 13 | { 14 | name: 'gmail-mcp', 15 | version: '1.0.0', 16 | }, 17 | { 18 | capabilities: { 19 | tools: {}, 20 | 21 | // ADD THIS: configurationSchema in capabilities 22 | configurationSchema: { 23 | environmentVariables: [ 24 | { 25 | name: 'GCP_OAUTH_KEYS_PATH', 26 | description: 'Path to the GCP OAuth keys JSON file', 27 | type: 'path', 28 | required: true, 29 | sensitive: true, 30 | }, 31 | { 32 | name: 'CREDENTIALS_PATH', 33 | description: 'Path to the stored credentials JSON file', 34 | type: 'path', 35 | required: true, 36 | sensitive: true, 37 | }, 38 | ], 39 | }, 40 | }, 41 | } 42 | ); 43 | 44 | // The rest of your MCP server code... 45 | server.setRequestHandler(ListToolsRequestSchema, async () => { 46 | return { 47 | tools: [ 48 | // Your tools... 49 | ], 50 | }; 51 | }); 52 | 53 | async function main() { 54 | const transport = new StdioServerTransport(); 55 | await server.connect(transport); 56 | } 57 | 58 | main(); 59 | 60 | /** 61 | * What this does: 62 | * 63 | * 1. MCP client (like NCP) calls initialize() 64 | * 2. Server returns InitializeResult with configurationSchema 65 | * 3. Client sees schema and prompts user for required config 66 | * 4. User provides GCP_OAUTH_KEYS_PATH and CREDENTIALS_PATH 67 | * 5. Client sets environment variables and starts server 68 | * 69 | * Before (without schema): 70 | * Error: GCP_OAUTH_KEYS_PATH is required 71 | * (User has to figure out what's needed) 72 | * 73 | * After (with schema): 74 | * 📋 Configuration needed: 75 | * GCP_OAUTH_KEYS_PATH: [required, path] 76 | * Path to the GCP OAuth keys JSON file 77 | * Enter GCP_OAUTH_KEYS_PATH: _ 78 | * (User is guided through setup) 79 | */ 80 | ``` -------------------------------------------------------------------------------- /src/services/tool-schema-parser.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared service for parsing tool schemas 3 | * Single source of truth for parameter extraction 4 | */ 5 | 6 | export interface ParameterInfo { 7 | name: string; 8 | type: string; 9 | required: boolean; 10 | description?: string; 11 | } 12 | 13 | export class ToolSchemaParser { 14 | /** 15 | * Parse parameters from a tool schema 16 | */ 17 | static parseParameters(schema: any): ParameterInfo[] { 18 | const params: ParameterInfo[] = []; 19 | 20 | if (!schema || typeof schema !== 'object') { 21 | return params; 22 | } 23 | 24 | const properties = schema.properties || {}; 25 | const required = schema.required || []; 26 | 27 | for (const [name, prop] of Object.entries(properties)) { 28 | const propDef = prop as any; 29 | params.push({ 30 | name, 31 | type: propDef.type || 'unknown', 32 | required: required.includes(name), 33 | description: propDef.description 34 | }); 35 | } 36 | 37 | return params; 38 | } 39 | 40 | /** 41 | * Get only required parameters from a schema 42 | */ 43 | static getRequiredParameters(schema: any): ParameterInfo[] { 44 | return this.parseParameters(schema).filter(p => p.required); 45 | } 46 | 47 | /** 48 | * Get only optional parameters from a schema 49 | */ 50 | static getOptionalParameters(schema: any): ParameterInfo[] { 51 | return this.parseParameters(schema).filter(p => !p.required); 52 | } 53 | 54 | /** 55 | * Check if a schema has any required parameters 56 | */ 57 | static hasRequiredParameters(schema: any): boolean { 58 | if (!schema || typeof schema !== 'object') { 59 | return false; 60 | } 61 | 62 | const required = schema.required || []; 63 | return Array.isArray(required) && required.length > 0; 64 | } 65 | 66 | /** 67 | * Count total parameters in a schema 68 | */ 69 | static countParameters(schema: any): { total: number; required: number; optional: number } { 70 | const params = this.parseParameters(schema); 71 | const required = params.filter(p => p.required).length; 72 | 73 | return { 74 | total: params.length, 75 | required, 76 | optional: params.length - required 77 | }; 78 | } 79 | 80 | /** 81 | * Get parameter by name from schema 82 | */ 83 | static getParameter(schema: any, paramName: string): ParameterInfo | undefined { 84 | return this.parseParameters(schema).find(p => p.name === paramName); 85 | } 86 | } ``` -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_type: 7 | description: 'Release type' 8 | required: true 9 | default: 'minor' 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | dry_run: 16 | description: 'Dry run (test without publishing)' 17 | required: false 18 | default: false 19 | type: boolean 20 | 21 | permissions: 22 | contents: write 23 | issues: write 24 | pull-requests: write 25 | id-token: write # Required for OIDC npm publishing 26 | 27 | jobs: 28 | release: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Setup Node.js 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: '18' 41 | registry-url: 'https://registry.npmjs.org' 42 | 43 | - name: Install dependencies 44 | run: npm ci 45 | 46 | - name: Build project 47 | run: npm run build 48 | 49 | - name: Run tests 50 | run: npm test 51 | 52 | - name: Test package functionality 53 | run: npm run test:package 54 | 55 | - name: Build Desktop Extension (DXT) 56 | run: npm run build:dxt 57 | 58 | - name: Get package version 59 | id: get_version 60 | run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT 61 | 62 | - name: Configure Git 63 | run: | 64 | git config --global user.name "GitHub Actions" 65 | git config --global user.email "[email protected]" 66 | 67 | - name: Release (Dry Run) 68 | if: ${{ inputs.dry_run == true }} 69 | run: npm run release -- --increment=${{ inputs.release_type }} --dry-run --ci 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | 73 | - name: Release 74 | if: ${{ inputs.dry_run == false }} 75 | run: npm run release -- --increment=${{ inputs.release_type }} --ci 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | 79 | - name: Upload Desktop Extension to release 80 | if: ${{ inputs.dry_run == false }} 81 | uses: softprops/action-gh-release@v1 82 | with: 83 | files: ncp.dxt 84 | tag_name: ${{ steps.get_version.outputs.version }} 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` -------------------------------------------------------------------------------- /src/utils/ncp-paths.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * NCP Paths Utility 3 | * Determines whether to use local or global .ncp directory 4 | */ 5 | 6 | import * as path from 'path'; 7 | import { readFileSync, existsSync } from 'fs'; 8 | import * as os from 'os'; 9 | 10 | let _ncpBaseDir: string | null = null; 11 | let _overrideWorkingDirectory: string | null = null; 12 | 13 | /** 14 | * Set override working directory for profile resolution 15 | * This allows the --working-dir parameter to override process.cwd() 16 | */ 17 | export function setOverrideWorkingDirectory(dir: string | null): void { 18 | _overrideWorkingDirectory = dir; 19 | // Clear cached base directory so it gets recalculated with new working directory 20 | _ncpBaseDir = null; 21 | } 22 | 23 | /** 24 | * Get the effective working directory (override or process.cwd()) 25 | */ 26 | export function getEffectiveWorkingDirectory(): string { 27 | return _overrideWorkingDirectory || process.cwd(); 28 | } 29 | 30 | /** 31 | * Determines the base .ncp directory to use 32 | * Only uses local .ncp if directory already exists, otherwise falls back to global ~/.ncp 33 | */ 34 | export function getNcpBaseDirectory(): string { 35 | if (_ncpBaseDir) return _ncpBaseDir; 36 | 37 | // Start from effective working directory and traverse up 38 | let currentDir = getEffectiveWorkingDirectory(); 39 | const root = path.parse(currentDir).root; 40 | 41 | while (currentDir !== root) { 42 | const localNcpDir = path.join(currentDir, '.ncp'); 43 | 44 | // Only use local .ncp if the directory already exists 45 | if (existsSync(localNcpDir)) { 46 | _ncpBaseDir = localNcpDir; 47 | return _ncpBaseDir; 48 | } 49 | 50 | currentDir = path.dirname(currentDir); 51 | } 52 | 53 | // Fallback to global ~/.ncp directory (will be created if needed) 54 | _ncpBaseDir = path.join(os.homedir(), '.ncp'); 55 | return _ncpBaseDir; 56 | } 57 | 58 | /** 59 | * Get the profiles directory (local or global) 60 | */ 61 | export function getProfilesDirectory(): string { 62 | return path.join(getNcpBaseDirectory(), 'profiles'); 63 | } 64 | 65 | /** 66 | * Get the cache directory (local or global) 67 | */ 68 | export function getCacheDirectory(): string { 69 | return path.join(getNcpBaseDirectory(), 'cache'); 70 | } 71 | 72 | /** 73 | * Get the logs directory (local or global) 74 | */ 75 | export function getLogsDirectory(): string { 76 | return path.join(getNcpBaseDirectory(), 'logs'); 77 | } 78 | 79 | /** 80 | * Check if we're using a local NCP installation 81 | */ 82 | export function isLocalNcpInstallation(): boolean { 83 | const baseDir = getNcpBaseDirectory(); 84 | return !baseDir.startsWith(os.homedir()); 85 | } ``` -------------------------------------------------------------------------------- /src/utils/markdown-renderer.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Markdown-to-Terminal Renderer 3 | * Converts markdown content to beautiful colored terminal output 4 | */ 5 | 6 | // ANSI Color codes 7 | const colors = { 8 | reset: '\x1b[0m', 9 | bold: '\x1b[1m', 10 | dim: '\x1b[2m', 11 | italic: '\x1b[3m', 12 | underline: '\x1b[4m', 13 | 14 | // Colors 15 | black: '\x1b[30m', 16 | red: '\x1b[31m', 17 | green: '\x1b[32m', 18 | yellow: '\x1b[33m', 19 | blue: '\x1b[34m', 20 | magenta: '\x1b[35m', 21 | cyan: '\x1b[36m', 22 | white: '\x1b[37m', 23 | gray: '\x1b[90m', 24 | 25 | // Bright colors 26 | brightRed: '\x1b[91m', 27 | brightGreen: '\x1b[92m', 28 | brightYellow: '\x1b[93m', 29 | brightBlue: '\x1b[94m', 30 | brightMagenta: '\x1b[95m', 31 | brightCyan: '\x1b[96m', 32 | brightWhite: '\x1b[97m' 33 | }; 34 | 35 | /** 36 | * Simple markdown-to-terminal renderer using ANSI codes 37 | */ 38 | export function renderMarkdown(content: string): string { 39 | let rendered = content; 40 | 41 | // Bold text: **text** or __text__ 42 | rendered = rendered.replace(/\*\*(.*?)\*\*/g, `${colors.bold}$1${colors.reset}`); 43 | rendered = rendered.replace(/__(.*?)__/g, `${colors.bold}$1${colors.reset}`); 44 | 45 | // Italic text: *text* or _text_ 46 | rendered = rendered.replace(/\*(.*?)\*/g, `${colors.italic}$1${colors.reset}`); 47 | rendered = rendered.replace(/_(.*?)_/g, `${colors.italic}$1${colors.reset}`); 48 | 49 | // Inline code: `code` 50 | rendered = rendered.replace(/`(.*?)`/g, `${colors.yellow}$1${colors.reset}`); 51 | 52 | // Headers: # ## ### 53 | rendered = rendered.replace(/^(#{1,6})\s+(.*)$/gm, (match, hashes, text) => { 54 | const level = hashes.length; 55 | const color = level <= 2 ? colors.brightCyan : colors.cyan; 56 | return `${color}${colors.bold}${text}${colors.reset}`; 57 | }); 58 | 59 | return rendered; 60 | } 61 | 62 | /** 63 | * Enhanced output with emoji and color support 64 | */ 65 | export function enhancedOutput(content: string): string { 66 | // First apply markdown rendering 67 | let rendered = renderMarkdown(content); 68 | 69 | // Additional terminal enhancements 70 | rendered = rendered 71 | // Enhance search indicators 72 | .replace(/🔍/g, '\x1b[36m🔍\x1b[0m') // Cyan search icon 73 | .replace(/📁/g, '\x1b[33m📁\x1b[0m') // Yellow folder icon 74 | .replace(/📋/g, '\x1b[32m📋\x1b[0m') // Green clipboard icon 75 | .replace(/💡/g, '\x1b[93m💡\x1b[0m') // Bright yellow tip icon 76 | .replace(/📄/g, '\x1b[34m📄\x1b[0m') // Blue navigation icon 77 | .replace(/✅/g, '\x1b[32m✅\x1b[0m') // Green success 78 | .replace(/❌/g, '\x1b[31m❌\x1b[0m') // Red error 79 | .replace(/🚀/g, '\x1b[35m🚀\x1b[0m'); // Magenta rocket 80 | 81 | return rendered; 82 | } ``` -------------------------------------------------------------------------------- /src/internal-mcps/internal-mcp-manager.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Internal MCP Manager 3 | * 4 | * Manages MCPs that are implemented internally by NCP 5 | * These appear in tool discovery like external MCPs but are handled internally 6 | */ 7 | 8 | import { InternalMCP, InternalToolResult } from './types.js'; 9 | import { NCPManagementMCP } from './ncp-management.js'; 10 | import ProfileManager from '../profiles/profile-manager.js'; 11 | import { logger } from '../utils/logger.js'; 12 | 13 | export class InternalMCPManager { 14 | private internalMCPs: Map<string, InternalMCP> = new Map(); 15 | 16 | constructor() { 17 | // Register internal MCPs 18 | this.registerInternalMCP(new NCPManagementMCP()); 19 | } 20 | 21 | /** 22 | * Register an internal MCP 23 | */ 24 | private registerInternalMCP(mcp: InternalMCP): void { 25 | this.internalMCPs.set(mcp.name, mcp); 26 | logger.debug(`Registered internal MCP: ${mcp.name}`); 27 | } 28 | 29 | /** 30 | * Initialize internal MCPs with ProfileManager 31 | */ 32 | initialize(profileManager: ProfileManager): void { 33 | for (const mcp of this.internalMCPs.values()) { 34 | if ('setProfileManager' in mcp && typeof mcp.setProfileManager === 'function') { 35 | mcp.setProfileManager(profileManager); 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * Get all internal MCPs for tool discovery 42 | */ 43 | getAllInternalMCPs(): InternalMCP[] { 44 | return Array.from(this.internalMCPs.values()); 45 | } 46 | 47 | /** 48 | * Execute a tool from an internal MCP 49 | * @param mcpName The internal MCP name (e.g., "ncp") 50 | * @param toolName The tool name (e.g., "add") 51 | * @param parameters Tool parameters 52 | */ 53 | async executeInternalTool( 54 | mcpName: string, 55 | toolName: string, 56 | parameters: any 57 | ): Promise<InternalToolResult> { 58 | const mcp = this.internalMCPs.get(mcpName); 59 | 60 | if (!mcp) { 61 | return { 62 | success: false, 63 | error: `Internal MCP not found: ${mcpName}` 64 | }; 65 | } 66 | 67 | try { 68 | return await mcp.executeTool(toolName, parameters); 69 | } catch (error: any) { 70 | logger.error(`Internal tool execution failed: ${mcpName}:${toolName} - ${error.message}`); 71 | return { 72 | success: false, 73 | error: error.message || 'Internal tool execution failed' 74 | }; 75 | } 76 | } 77 | 78 | /** 79 | * Check if an MCP is internal 80 | */ 81 | isInternalMCP(mcpName: string): boolean { 82 | return this.internalMCPs.has(mcpName); 83 | } 84 | 85 | /** 86 | * Get tool names for a specific internal MCP 87 | */ 88 | getInternalMCPTools(mcpName: string): string[] { 89 | const mcp = this.internalMCPs.get(mcpName); 90 | return mcp ? mcp.tools.map(t => t.name) : []; 91 | } 92 | } 93 | ``` -------------------------------------------------------------------------------- /.github/workflows/publish-mcp-registry.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Publish to MCP Registry 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write # Required for GitHub OIDC authentication with MCP registry 10 | 11 | jobs: 12 | publish-to-mcp-registry: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Get version from tag 19 | id: get_version 20 | run: | 21 | VERSION=${GITHUB_REF#refs/tags/} 22 | echo "version=$VERSION" >> $GITHUB_OUTPUT 23 | echo "Publishing version: $VERSION" 24 | 25 | - name: Update server.json with release version 26 | run: | 27 | VERSION=${{ steps.get_version.outputs.version }} 28 | jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' server.json > server.json.tmp 29 | mv server.json.tmp server.json 30 | cat server.json 31 | 32 | - name: Validate server.json 33 | run: | 34 | curl -sS https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json -o /tmp/server.schema.json 35 | # Basic JSON validation 36 | jq empty server.json 37 | echo "✓ server.json is valid JSON" 38 | 39 | - name: Download MCP Publisher 40 | run: | 41 | VERSION="v1.1.0" 42 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') 43 | ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') 44 | 45 | curl -L "https://github.com/modelcontextprotocol/registry/releases/download/${VERSION}/mcp-publisher_${VERSION#v}_${OS}_${ARCH}.tar.gz" | tar xz 46 | chmod +x mcp-publisher 47 | ./mcp-publisher --version 48 | 49 | - name: Login to MCP Registry 50 | run: | 51 | # Try GitHub OIDC first (preferred, no secrets needed) 52 | if ./mcp-publisher login github-oidc; then 53 | echo "✓ Authenticated via GitHub OIDC" 54 | elif [ -n "${{ secrets.MCP_GITHUB_TOKEN }}" ]; then 55 | # Fallback to GitHub PAT if OIDC fails or org not detected 56 | echo "⚠ OIDC failed, falling back to GitHub PAT" 57 | echo "${{ secrets.MCP_GITHUB_TOKEN }}" | ./mcp-publisher login github --token-stdin 58 | else 59 | echo "❌ Authentication failed. See RELEASE.md for setup instructions." 60 | exit 1 61 | fi 62 | 63 | - name: Publish to MCP Registry 64 | run: ./mcp-publisher publish 65 | 66 | - name: Summary 67 | run: | 68 | echo "✅ Successfully published NCP v${{ steps.get_version.outputs.version }} to MCP Registry" 69 | echo "📦 Package: io.github.portel-dev/ncp" 70 | echo "🔗 NPM: https://www.npmjs.com/package/@portel/ncp" 71 | ``` -------------------------------------------------------------------------------- /test/session-id-passthrough.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Test for Session ID Transparency 3 | * Verifies that _meta (including session_id) is forwarded transparently to MCP servers 4 | */ 5 | 6 | import { NCPOrchestrator } from '../src/orchestrator/ncp-orchestrator.js'; 7 | import { MCPServer } from '../src/server/mcp-server.js'; 8 | 9 | describe('Session ID Passthrough', () => { 10 | it('should forward _meta field from client to orchestrator', async () => { 11 | const server = new MCPServer('default', false, false); 12 | await server.initialize(); 13 | 14 | // Simulate client request with session_id in _meta 15 | const request = { 16 | jsonrpc: '2.0' as const, 17 | id: 1, 18 | method: 'tools/call', 19 | params: { 20 | name: 'run', 21 | arguments: { 22 | tool: 'chrome-devtools:list_pages', 23 | parameters: {} 24 | }, 25 | _meta: { 26 | session_id: 'test_session_123', 27 | custom_field: 'custom_value' 28 | } 29 | } 30 | }; 31 | 32 | const response = await server.handleRequest(request); 33 | 34 | // Should not error - _meta should be forwarded transparently 35 | expect(response).toBeDefined(); 36 | // May error if chrome-devtools not available, but shouldn't crash on _meta 37 | if (response?.error) { 38 | // Error should be about MCP availability, not _meta handling 39 | expect(response.error.message).not.toContain('_meta'); 40 | } 41 | }); 42 | 43 | it('should work without _meta (backwards compatibility)', async () => { 44 | const server = new MCPServer('default', false, false); 45 | await server.initialize(); 46 | 47 | // Simulate client request WITHOUT _meta 48 | const request = { 49 | jsonrpc: '2.0' as const, 50 | id: 1, 51 | method: 'tools/call', 52 | params: { 53 | name: 'run', 54 | arguments: { 55 | tool: 'chrome-devtools:list_pages', 56 | parameters: {} 57 | } 58 | // No _meta field 59 | } 60 | }; 61 | 62 | const response = await server.handleRequest(request); 63 | 64 | // Should still work - _meta is optional 65 | expect(response).toBeDefined(); 66 | // Should not crash when _meta is absent 67 | }); 68 | 69 | it('should forward empty _meta object', async () => { 70 | const server = new MCPServer('default', false, false); 71 | await server.initialize(); 72 | 73 | const request = { 74 | jsonrpc: '2.0' as const, 75 | id: 1, 76 | method: 'tools/call', 77 | params: { 78 | name: 'run', 79 | arguments: { 80 | tool: 'chrome-devtools:list_pages', 81 | parameters: {} 82 | }, 83 | _meta: {} // Empty _meta 84 | } 85 | }; 86 | 87 | const response = await server.handleRequest(request); 88 | 89 | expect(response).toBeDefined(); 90 | // Should handle empty _meta gracefully 91 | }); 92 | }); 93 | ``` -------------------------------------------------------------------------------- /docs/clients/cursor.md: -------------------------------------------------------------------------------- ```markdown 1 | # Installing NCP on Cursor 2 | 3 | **Method:** JSON configuration only 4 | 5 | --- 6 | 7 | ## 📋 Overview 8 | 9 | Cursor IDE supports MCP servers via JSON configuration. Configure NCP once and access all your MCP tools through a unified interface. 10 | 11 | --- 12 | 13 | ## 🔧 Installation Steps 14 | 15 | ### 1. Install NCP 16 | 17 | ```bash 18 | npm install -g @portel/ncp 19 | ``` 20 | 21 | ### 2. Import Your Existing MCPs (Optional) 22 | 23 | ```bash 24 | # If you have MCPs configured elsewhere, copy config to clipboard 25 | # Then run: 26 | ncp config import 27 | ``` 28 | 29 | ### 3. Add MCPs to NCP 30 | 31 | ```bash 32 | # Add your MCPs 33 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 34 | ncp add github npx @modelcontextprotocol/server-github 35 | ncp add brave-search npx @modelcontextprotocol/server-brave-search 36 | 37 | # Verify 38 | ncp list 39 | ``` 40 | 41 | ### 4. Configure Cursor 42 | 43 | **Config file location:** 44 | - **macOS:** `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 45 | - **Windows:** `%APPDATA%/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 46 | - **Linux:** `~/.config/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 47 | 48 | **Edit the file:** 49 | ```bash 50 | # macOS 51 | nano ~/Library/Application\ Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json 52 | ``` 53 | 54 | **Replace contents with:** 55 | ```json 56 | { 57 | "mcpServers": { 58 | "ncp": { 59 | "command": "ncp" 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | ### 5. Restart Cursor 66 | 67 | 1. Quit Cursor completely (⌘Q on Mac, Alt+F4 on Windows) 68 | 2. Reopen Cursor 69 | 3. Start using NCP in your AI chat 70 | 71 | --- 72 | 73 | ## 🎯 Using NCP in Cursor 74 | 75 | Ask your AI assistant: 76 | - "List all available MCP tools" 77 | - "Find tools to read files" 78 | - "Execute filesystem:read_file on /tmp/test.txt" 79 | 80 | --- 81 | 82 | ## 🐛 Troubleshooting 83 | 84 | **NCP command not found:** 85 | ```bash 86 | npm install -g @portel/ncp 87 | ncp --version 88 | ``` 89 | 90 | **Cursor doesn't see NCP:** 91 | 1. Verify config file path is correct for your OS 92 | 2. Check JSON syntax is valid 93 | 3. Restart Cursor completely 94 | 4. Check Cursor's developer console for errors 95 | 96 | **NCP shows no MCPs:** 97 | ```bash 98 | ncp list 99 | # If empty, add MCPs: 100 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 101 | ``` 102 | 103 | --- 104 | 105 | ## 📚 More Resources 106 | 107 | - **[Claude Desktop Guide](./claude-desktop.md)** - For Claude Desktop users 108 | - **[Main README](../../README.md)** - Full documentation 109 | - **[Cursor Documentation](https://cursor.sh/docs)** - Official Cursor docs 110 | 111 | --- 112 | 113 | ## 🤝 Need Help? 114 | 115 | - **GitHub Issues:** [Report bugs](https://github.com/portel-dev/ncp/issues) 116 | - **GitHub Discussions:** [Ask questions](https://github.com/portel-dev/ncp/discussions) 117 | ``` -------------------------------------------------------------------------------- /src/utils/text-utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared utilities for text processing and formatting 3 | * Consolidates text wrapping and formatting logic 4 | */ 5 | 6 | export interface TextWrapOptions { 7 | maxWidth: number; 8 | indent?: string; 9 | cleanupPrefixes?: boolean; 10 | preserveWhitespace?: boolean; 11 | } 12 | 13 | export class TextUtils { 14 | /** 15 | * Wrap text to fit within specified width with optional indentation 16 | */ 17 | static wrapText(text: string, options: TextWrapOptions): string { 18 | const { maxWidth, indent = '', cleanupPrefixes = false, preserveWhitespace = false } = options; 19 | 20 | if (!text) { 21 | return text; 22 | } 23 | 24 | // Clean up text if requested (used by MCP server) 25 | let processedText = text; 26 | if (cleanupPrefixes) { 27 | processedText = text 28 | .replace(/^[^:]+:\s*/, '') // Remove "desktop-commander: " prefix 29 | .replace(/\s+/g, ' ') // Replace multiple whitespace with single space 30 | .trim(); 31 | } else if (!preserveWhitespace) { 32 | // Basic cleanup for CLI usage 33 | processedText = text.trim(); 34 | } 35 | 36 | // If text fits within width, return as-is 37 | if (processedText.length <= maxWidth) { 38 | return processedText; 39 | } 40 | 41 | // Split into words and wrap 42 | const words = processedText.split(' '); 43 | const lines: string[] = []; 44 | let currentLine = ''; 45 | 46 | for (const word of words) { 47 | const testLine = currentLine ? `${currentLine} ${word}` : word; 48 | 49 | if (testLine.length <= maxWidth) { 50 | currentLine = testLine; 51 | } else { 52 | if (currentLine) { 53 | lines.push(currentLine); 54 | currentLine = word; 55 | } else { 56 | // Single word longer than maxWidth 57 | lines.push(word); 58 | } 59 | } 60 | } 61 | 62 | if (currentLine) { 63 | lines.push(currentLine); 64 | } 65 | 66 | // Join lines with proper indentation for continuation 67 | return lines.map((line, index) => 68 | index === 0 ? line : `\n${indent}${line}` 69 | ).join(''); 70 | } 71 | 72 | /** 73 | * Wrap text with background color applied to each line (CLI-specific) 74 | */ 75 | static wrapTextWithBackground( 76 | text: string, 77 | maxWidth: number, 78 | indent: string, 79 | backgroundFormatter: (text: string) => string 80 | ): string { 81 | if (text.length <= maxWidth) { 82 | return `${indent}${backgroundFormatter(text)}`; 83 | } 84 | 85 | const wrappedText = this.wrapText(text, { maxWidth, indent: '', preserveWhitespace: true }); 86 | const lines = wrappedText.split('\n'); 87 | 88 | return lines.map((line, index) => { 89 | const formattedLine = backgroundFormatter(line); 90 | return index === 0 ? `${indent}${formattedLine}` : `${indent}${formattedLine}`; 91 | }).join('\n'); 92 | } 93 | } ``` -------------------------------------------------------------------------------- /docs/clients/cline.md: -------------------------------------------------------------------------------- ```markdown 1 | # Installing NCP on Cline (VS Code Extension) 2 | 3 | **Method:** JSON configuration only 4 | 5 | --- 6 | 7 | ## 📋 Overview 8 | 9 | Cline (formerly Claude Dev) is a VS Code extension that supports MCP servers. Configure NCP to unify all your MCP tools under one intelligent interface. 10 | 11 | --- 12 | 13 | ## 🔧 Installation Steps 14 | 15 | ### 1. Install NCP 16 | 17 | ```bash 18 | npm install -g @portel/ncp 19 | ``` 20 | 21 | ### 2. Import Your Existing MCPs (Optional) 22 | 23 | ```bash 24 | # If you have MCPs configured elsewhere, copy config to clipboard 25 | # Then run: 26 | ncp config import 27 | ``` 28 | 29 | ### 3. Add MCPs to NCP 30 | 31 | ```bash 32 | # Add your MCPs 33 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 34 | ncp add github npx @modelcontextprotocol/server-github 35 | ncp add sequential-thinking npx @modelcontextprotocol/server-sequential-thinking 36 | 37 | # Verify 38 | ncp list 39 | ``` 40 | 41 | ### 4. Configure Cline 42 | 43 | **Config file location:** 44 | - **macOS:** `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 45 | - **Windows:** `%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 46 | - **Linux:** `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` 47 | 48 | **Edit the file:** 49 | ```bash 50 | # macOS 51 | nano ~/Library/Application\ Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json 52 | ``` 53 | 54 | **Replace contents with:** 55 | ```json 56 | { 57 | "mcpServers": { 58 | "ncp": { 59 | "command": "ncp" 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | ### 5. Restart VS Code 66 | 67 | 1. Close all VS Code windows 68 | 2. Reopen VS Code 69 | 3. Open Cline extension 70 | 4. Start using NCP 71 | 72 | --- 73 | 74 | ## 🎯 Using NCP in Cline 75 | 76 | In Cline chat, ask: 77 | - "List all available MCP tools using NCP" 78 | - "Find tools for file operations" 79 | - "Use NCP to search for GitHub-related tools" 80 | 81 | --- 82 | 83 | ## 🐛 Troubleshooting 84 | 85 | **NCP command not found:** 86 | ```bash 87 | npm install -g @portel/ncp 88 | ncp --version 89 | ``` 90 | 91 | **Cline doesn't see NCP:** 92 | 1. Verify config file exists and path is correct 93 | 2. Check JSON syntax is valid 94 | 3. Restart VS Code completely (close all windows) 95 | 4. Check VS Code's Developer Tools console for errors 96 | 97 | **NCP shows no MCPs:** 98 | ```bash 99 | ncp list 100 | # If empty, add MCPs: 101 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 102 | ``` 103 | 104 | --- 105 | 106 | ## 📚 More Resources 107 | 108 | - **[Claude Desktop Guide](./claude-desktop.md)** - For Claude Desktop users 109 | - **[Main README](../../README.md)** - Full documentation 110 | - **[Cline Extension](https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev)** - VS Code Marketplace 111 | 112 | --- 113 | 114 | ## 🤝 Need Help? 115 | 116 | - **GitHub Issues:** [Report bugs](https://github.com/portel-dev/ncp/issues) 117 | - **GitHub Discussions:** [Ask questions](https://github.com/portel-dev/ncp/discussions) 118 | ``` -------------------------------------------------------------------------------- /src/utils/progress-spinner.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simple CLI Progress Spinner 3 | * Shows animated progress during long-running operations 4 | */ 5 | 6 | import chalk from 'chalk'; 7 | 8 | export class ProgressSpinner { 9 | private interval: NodeJS.Timeout | null = null; 10 | private frame = 0; 11 | private message = ''; 12 | private subMessage = ''; 13 | private isSpinning = false; 14 | private isFirstRender = true; 15 | 16 | private readonly frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; 17 | 18 | start(message: string): void { 19 | if (this.isSpinning) { 20 | this.stop(); 21 | } 22 | 23 | this.message = message; 24 | this.subMessage = ''; 25 | this.frame = 0; 26 | this.isSpinning = true; 27 | this.isFirstRender = true; 28 | 29 | // Add blank line before spinner starts (don't overwrite command line) 30 | process.stdout.write('\n'); 31 | 32 | // Hide cursor 33 | process.stdout.write('\u001B[?25l'); 34 | 35 | this.interval = setInterval(() => { 36 | this.render(); 37 | this.frame = (this.frame + 1) % this.frames.length; 38 | }, 80); 39 | 40 | this.render(); 41 | } 42 | 43 | updateMessage(message: string, subMessage?: string): void { 44 | if (!this.isSpinning) return; 45 | 46 | this.message = message; 47 | if (subMessage !== undefined) { 48 | this.subMessage = subMessage; 49 | } 50 | } 51 | 52 | updateSubMessage(subMessage: string): void { 53 | if (!this.isSpinning) return; 54 | this.subMessage = subMessage; 55 | } 56 | 57 | stop(): void { 58 | if (!this.isSpinning) return; 59 | 60 | if (this.interval) { 61 | clearInterval(this.interval); 62 | this.interval = null; 63 | } 64 | 65 | this.isSpinning = false; 66 | 67 | // Clear the current line(s) 68 | this.clearLines(); 69 | 70 | // Show cursor 71 | process.stdout.write('\u001B[?25h'); 72 | } 73 | 74 | success(message: string): void { 75 | this.stop(); 76 | console.log(chalk.green(`✅ ${message}`)); 77 | } 78 | 79 | error(message: string): void { 80 | this.stop(); 81 | console.log(chalk.red(`❌ ${message}`)); 82 | } 83 | 84 | private render(): void { 85 | if (!this.isSpinning) return; 86 | 87 | // Clear previous output (skip on first render to preserve newline) 88 | if (!this.isFirstRender) { 89 | this.clearLines(); 90 | } else { 91 | this.isFirstRender = false; 92 | } 93 | 94 | // Main spinner line 95 | const spinnerChar = chalk.cyan(this.frames[this.frame]); 96 | const mainLine = `${spinnerChar} ${chalk.white(this.message)}`; 97 | 98 | process.stdout.write(mainLine); 99 | 100 | // Sub-message line (debug logs) 101 | if (this.subMessage) { 102 | const subLine = `\n ${chalk.dim(this.subMessage)}`; 103 | process.stdout.write(subLine); 104 | } 105 | } 106 | 107 | private clearLines(): void { 108 | // Move to beginning of line and clear 109 | process.stdout.write('\r\u001B[K'); 110 | 111 | // If there was a sub-message, clear the previous line too 112 | if (this.subMessage) { 113 | process.stdout.write('\u001B[A\r\u001B[K'); 114 | } 115 | } 116 | } 117 | 118 | // Export a singleton instance for convenience 119 | export const spinner = new ProgressSpinner(); ``` -------------------------------------------------------------------------------- /MCP-CONFIGURATION-SCHEMA-FORMAT.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "comment": "MCP InitializeResult with configurationSchema", 3 | "description": "This is the JSON format MCP servers return in their initialize() response", 4 | 5 | "protocolVersion": "0.1.0", 6 | "capabilities": { 7 | "tools": {}, 8 | "resources": {}, 9 | "prompts": {} 10 | }, 11 | "serverInfo": { 12 | "name": "example-mcp-server", 13 | "version": "1.0.0" 14 | }, 15 | "instructions": "Optional human-readable setup instructions", 16 | 17 | "configurationSchema": { 18 | "comment": "This is the configuration schema that clients like NCP will use", 19 | 20 | "environmentVariables": [ 21 | { 22 | "name": "API_KEY", 23 | "description": "API key for service authentication", 24 | "type": "string", 25 | "required": true, 26 | "sensitive": true, 27 | "pattern": "^[a-zA-Z0-9]{32}$", 28 | "examples": ["abcd1234efgh5678ijkl9012mnop3456"] 29 | }, 30 | { 31 | "name": "DATABASE_URL", 32 | "description": "PostgreSQL database connection URL", 33 | "type": "url", 34 | "required": true, 35 | "sensitive": true, 36 | "pattern": "^postgresql://.*", 37 | "examples": ["postgresql://user:pass@localhost:5432/dbname"] 38 | }, 39 | { 40 | "name": "CONFIG_PATH", 41 | "description": "Path to configuration file", 42 | "type": "path", 43 | "required": false, 44 | "default": "~/.config/app.json", 45 | "examples": ["/etc/app/config.json", "~/.config/app.json"] 46 | }, 47 | { 48 | "name": "LOG_LEVEL", 49 | "description": "Logging level (debug, info, warn, error)", 50 | "type": "string", 51 | "required": false, 52 | "default": "info", 53 | "examples": ["debug", "info", "warn", "error"] 54 | }, 55 | { 56 | "name": "MAX_RETRIES", 57 | "description": "Maximum number of retry attempts", 58 | "type": "number", 59 | "required": false, 60 | "default": 3, 61 | "examples": [3, 5, 10] 62 | }, 63 | { 64 | "name": "ENABLE_CACHE", 65 | "description": "Enable response caching", 66 | "type": "boolean", 67 | "required": false, 68 | "default": true, 69 | "examples": [true, false] 70 | } 71 | ], 72 | 73 | "arguments": [ 74 | { 75 | "name": "allowed-directory", 76 | "description": "Directories that the MCP is allowed to access", 77 | "type": "path", 78 | "required": true, 79 | "multiple": true, 80 | "examples": ["/home/user/documents", "/var/data"] 81 | }, 82 | { 83 | "name": "port", 84 | "description": "Port number to listen on", 85 | "type": "number", 86 | "required": false, 87 | "default": 8080, 88 | "examples": [8080, 3000, 9000] 89 | } 90 | ], 91 | 92 | "other": [ 93 | { 94 | "name": "custom-setting", 95 | "description": "Custom configuration setting", 96 | "type": "string", 97 | "required": false, 98 | "examples": ["value1", "value2"] 99 | } 100 | ] 101 | } 102 | } 103 | ``` -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Logger utility for NCP 3 | * 4 | * Controls logging based on context: 5 | * - When running as MCP server: minimal/no logging to stderr 6 | * - When running as CLI or debugging: full logging 7 | */ 8 | 9 | export class Logger { 10 | private static instance: Logger; 11 | private isMCPMode: boolean = false; 12 | private isCLIMode: boolean = false; 13 | private debugMode: boolean = false; 14 | 15 | private constructor() { 16 | // Check if running CLI commands (list, find, run, add, remove, config, etc.) 17 | this.isCLIMode = process.argv.some(arg => 18 | ['list', 'find', 'run', 'add', 'remove', 'config', '--help', 'help', '--version', '-v', '-h', 'import'].includes(arg) 19 | ); 20 | 21 | // Detect if running as MCP server - more reliable detection 22 | // MCP server mode: default when no CLI commands are provided 23 | this.isMCPMode = !this.isCLIMode || process.env.NCP_MODE === 'mcp'; 24 | 25 | // Enable debug mode ONLY if explicitly requested 26 | this.debugMode = process.env.NCP_DEBUG === 'true' || 27 | process.argv.includes('--debug'); 28 | } 29 | 30 | static getInstance(): Logger { 31 | if (!Logger.instance) { 32 | Logger.instance = new Logger(); 33 | } 34 | return Logger.instance; 35 | } 36 | 37 | /** 38 | * Log informational messages 39 | * Completely suppressed in MCP mode and CLI mode unless debugging 40 | */ 41 | info(message: string): void { 42 | if (this.debugMode) { 43 | console.error(`[NCP] ${message}`); 44 | } 45 | } 46 | 47 | /** 48 | * Log only essential startup messages in MCP mode 49 | */ 50 | mcpInfo(message: string): void { 51 | if (this.debugMode) { 52 | console.error(`[NCP] ${message}`); 53 | } 54 | // In MCP mode and CLI mode, stay completely silent unless debugging 55 | } 56 | 57 | /** 58 | * Log debug messages 59 | * Only shown in debug mode 60 | */ 61 | debug(message: string): void { 62 | if (this.debugMode) { 63 | console.error(`[NCP DEBUG] ${message}`); 64 | } 65 | } 66 | 67 | /** 68 | * Log error messages 69 | * Always shown (but minimal in MCP mode) 70 | */ 71 | error(message: string, error?: any): void { 72 | if (this.isMCPMode && !this.debugMode) { 73 | // In MCP mode, only log critical errors 74 | if (error?.critical) { 75 | console.error(`[NCP ERROR] ${message}`); 76 | } 77 | } else { 78 | console.error(`[NCP ERROR] ${message}`); 79 | if (error) { 80 | console.error(error); 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Log warnings 87 | * Completely suppressed in MCP mode and CLI mode unless debugging 88 | */ 89 | warn(message: string): void { 90 | if (this.debugMode) { 91 | console.error(`[NCP WARN] ${message}`); 92 | } 93 | } 94 | 95 | /** 96 | * Log progress updates 97 | * Completely suppressed in MCP mode and CLI mode unless debugging 98 | */ 99 | progress(message: string): void { 100 | if (this.debugMode) { 101 | console.error(`[NCP] ${message}`); 102 | } 103 | } 104 | 105 | /** 106 | * Check if in MCP mode 107 | */ 108 | isInMCPMode(): boolean { 109 | return this.isMCPMode; 110 | } 111 | 112 | /** 113 | * Force enable/disable MCP mode 114 | */ 115 | setMCPMode(enabled: boolean): void { 116 | this.isMCPMode = enabled; 117 | } 118 | } 119 | 120 | // Singleton export 121 | export const logger = Logger.getInstance(); 122 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Bug Report 2 | description: File a bug report to help us improve NCP 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! Please provide as much detail as possible to help us reproduce and fix the issue. 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 is not a duplicate 18 | required: true 19 | - label: I am using a supported version of NCP (1.1.x or 1.2.x) 20 | required: true 21 | - label: I have read the documentation and troubleshooting guide 22 | required: true 23 | 24 | - type: input 25 | id: version 26 | attributes: 27 | label: NCP Version 28 | description: What version of NCP are you running? 29 | placeholder: "e.g., 1.2.1" 30 | validations: 31 | required: true 32 | 33 | - type: dropdown 34 | id: environment 35 | attributes: 36 | label: Environment 37 | description: What environment are you running NCP in? 38 | options: 39 | - macOS 40 | - Windows 41 | - Linux (Ubuntu) 42 | - Linux (other) 43 | - Docker 44 | - Other (specify in description) 45 | validations: 46 | required: true 47 | 48 | - type: input 49 | id: node-version 50 | attributes: 51 | label: Node.js Version 52 | description: What version of Node.js are you using? 53 | placeholder: "e.g., 20.11.0" 54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | id: description 59 | attributes: 60 | label: Bug Description 61 | description: A clear and concise description of what the bug is 62 | placeholder: Describe what happened and what you expected to happen 63 | validations: 64 | required: true 65 | 66 | - type: textarea 67 | id: reproduction 68 | attributes: 69 | label: Steps to Reproduce 70 | description: How can we reproduce this issue? 71 | placeholder: | 72 | 1. Run command '...' 73 | 2. Configure MCP server '...' 74 | 3. Execute action '...' 75 | 4. See error 76 | validations: 77 | required: true 78 | 79 | - type: textarea 80 | id: expected 81 | attributes: 82 | label: Expected Behavior 83 | description: What did you expect to happen? 84 | validations: 85 | required: true 86 | 87 | - type: textarea 88 | id: actual 89 | attributes: 90 | label: Actual Behavior 91 | description: What actually happened? 92 | validations: 93 | required: true 94 | 95 | - type: textarea 96 | id: logs 97 | attributes: 98 | label: Error Messages/Logs 99 | description: Please paste any relevant error messages or logs 100 | render: shell 101 | 102 | - type: textarea 103 | id: config 104 | attributes: 105 | label: NCP Configuration 106 | description: | 107 | Please share relevant parts of your NCP configuration (remove any sensitive information) 108 | render: json 109 | 110 | - type: textarea 111 | id: additional 112 | attributes: 113 | label: Additional Context 114 | description: Add any other context about the problem here 115 | placeholder: Screenshots, related issues, potential solutions, etc. ``` -------------------------------------------------------------------------------- /docs/clients/continue.md: -------------------------------------------------------------------------------- ```markdown 1 | # Installing NCP on Continue (VS Code Extension) 2 | 3 | **Method:** JSON configuration only 4 | 5 | --- 6 | 7 | ## 📋 Overview 8 | 9 | Continue is a VS Code extension that supports MCP servers via JSON configuration. Use NCP to consolidate all your MCP tools into a unified, intelligent interface. 10 | 11 | --- 12 | 13 | ## 🔧 Installation Steps 14 | 15 | ### 1. Install NCP 16 | 17 | ```bash 18 | npm install -g @portel/ncp 19 | ``` 20 | 21 | ### 2. Import Your Existing MCPs (Optional) 22 | 23 | ```bash 24 | # If you have MCPs configured elsewhere, copy config to clipboard 25 | # Then run: 26 | ncp config import 27 | ``` 28 | 29 | ### 3. Add MCPs to NCP 30 | 31 | ```bash 32 | # Add your MCPs 33 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 34 | ncp add github npx @modelcontextprotocol/server-github 35 | ncp add memory npx @modelcontextprotocol/server-memory 36 | 37 | # Verify 38 | ncp list 39 | ``` 40 | 41 | ### 4. Configure Continue 42 | 43 | **Config file location:** 44 | - **All platforms:** `~/.continue/config.json` 45 | 46 | **Edit the file:** 47 | ```bash 48 | nano ~/.continue/config.json 49 | ``` 50 | 51 | **Add NCP to the `experimental.modelContextProtocolServers` section:** 52 | ```json 53 | { 54 | "models": [...], 55 | "experimental": { 56 | "modelContextProtocolServers": { 57 | "ncp": { 58 | "command": "ncp" 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | > **Note:** Continue uses nested configuration under `experimental.modelContextProtocolServers`, not top-level `mcpServers`. 66 | 67 | ### 5. Restart VS Code 68 | 69 | 1. Close all VS Code windows 70 | 2. Reopen VS Code 71 | 3. Open Continue extension 72 | 4. Start using NCP 73 | 74 | --- 75 | 76 | ## 🎯 Using NCP in Continue 77 | 78 | In Continue chat, ask: 79 | - "List all available MCP tools" 80 | - "Find tools for reading files" 81 | - "Use NCP to discover GitHub tools" 82 | 83 | --- 84 | 85 | ## 🐛 Troubleshooting 86 | 87 | **NCP command not found:** 88 | ```bash 89 | npm install -g @portel/ncp 90 | ncp --version 91 | ``` 92 | 93 | **Continue doesn't see NCP:** 94 | 1. Verify `~/.continue/config.json` has correct structure 95 | 2. Ensure NCP is under `experimental.modelContextProtocolServers` 96 | 3. Check JSON syntax is valid 97 | 4. Restart VS Code completely 98 | 5. Check Continue extension logs 99 | 100 | **NCP shows no MCPs:** 101 | ```bash 102 | ncp list 103 | # If empty, add MCPs: 104 | ncp add filesystem npx @modelcontextprotocol/server-filesystem ~/Documents 105 | ``` 106 | 107 | --- 108 | 109 | ## 📝 Continue Config Format Reference 110 | 111 | **Correct format:** 112 | ```json 113 | { 114 | "models": [ 115 | { 116 | "title": "Claude 3.5 Sonnet", 117 | "provider": "anthropic", 118 | "model": "claude-3-5-sonnet-20241022", 119 | "apiKey": "..." 120 | } 121 | ], 122 | "experimental": { 123 | "modelContextProtocolServers": { 124 | "ncp": { 125 | "command": "ncp" 126 | } 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | **⚠️ Note the nested structure:** 133 | - `experimental` → `modelContextProtocolServers` → `ncp` 134 | 135 | --- 136 | 137 | ## 📚 More Resources 138 | 139 | - **[Claude Desktop Guide](./claude-desktop.md)** - For Claude Desktop users 140 | - **[Main README](../../README.md)** - Full documentation 141 | - **[Continue Extension](https://marketplace.visualstudio.com/items?itemName=Continue.continue)** - VS Code Marketplace 142 | - **[Continue Docs](https://docs.continue.dev/)** - Official Continue documentation 143 | 144 | --- 145 | 146 | ## 🤝 Need Help? 147 | 148 | - **GitHub Issues:** [Report bugs](https://github.com/portel-dev/ncp/issues) 149 | - **GitHub Discussions:** [Ask questions](https://github.com/portel-dev/ncp/discussions) 150 | ``` -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- ```markdown 1 | # Pull Request 2 | 3 | ## Description 4 | <!-- Provide a brief description of the changes in this PR --> 5 | 6 | ## Type of Change 7 | <!-- Mark the relevant option with [x] --> 8 | 9 | - [ ] 🐛 Bug fix (non-breaking change that fixes an issue) 10 | - [ ] ✨ New feature (non-breaking change that adds functionality) 11 | - [ ] 💥 Breaking change (fix or feature that causes existing functionality to not work as expected) 12 | - [ ] 📚 Documentation update 13 | - [ ] 🔧 Configuration change 14 | - [ ] ⚡ Performance improvement 15 | - [ ] 🧪 Test additions or improvements 16 | - [ ] 🎨 Code style/formatting changes 17 | - [ ] 📦 Dependencies update 18 | 19 | ## Related Issues 20 | <!-- Link to related issues. Use "Closes #123" to auto-close issues when PR is merged --> 21 | 22 | - Closes # 23 | - Related to # 24 | 25 | ## Changes Made 26 | <!-- Provide a detailed list of changes --> 27 | 28 | ### Added 29 | - 30 | 31 | ### Changed 32 | - 33 | 34 | ### Removed 35 | - 36 | 37 | ### Fixed 38 | - 39 | 40 | ## Testing 41 | <!-- Describe the testing you've performed --> 42 | 43 | ### Test Environment 44 | - [ ] Local development 45 | - [ ] Docker container 46 | - [ ] Multiple Node.js versions 47 | - [ ] Multiple operating systems 48 | 49 | ### Test Cases 50 | - [ ] Unit tests pass (`npm test`) 51 | - [ ] Integration tests pass 52 | - [ ] Manual testing completed 53 | - [ ] Edge cases considered 54 | 55 | ### Test Commands Used 56 | ```bash 57 | # Add commands used for testing 58 | npm test 59 | npm run build 60 | ``` 61 | 62 | ## MCP Server Compatibility 63 | <!-- If this affects MCP server integration --> 64 | 65 | - [ ] Tested with multiple MCP servers 66 | - [ ] No breaking changes to MCP protocol usage 67 | - [ ] Discovery/search functionality unaffected 68 | - [ ] Error handling improved/maintained 69 | 70 | ## Documentation 71 | <!-- Check if documentation needs updates --> 72 | 73 | - [ ] README.md updated (if needed) 74 | - [ ] API documentation updated 75 | - [ ] Configuration examples updated 76 | - [ ] Migration guide provided (for breaking changes) 77 | 78 | ## Performance Impact 79 | <!-- Describe any performance implications --> 80 | 81 | - [ ] No performance impact expected 82 | - [ ] Performance improvement included 83 | - [ ] Performance regression possible (explain below) 84 | 85 | <!-- If performance impact, provide details --> 86 | 87 | ## Screenshots/Examples 88 | <!-- Add screenshots or examples if UI/CLI changes are involved --> 89 | 90 | ## Checklist 91 | <!-- Review checklist before submitting --> 92 | 93 | ### Code Quality 94 | - [ ] Code follows project style guidelines 95 | - [ ] Self-review completed 96 | - [ ] Code is properly commented 97 | - [ ] No console.log or debug statements left 98 | - [ ] Error handling is appropriate 99 | 100 | ### Security 101 | - [ ] No sensitive information exposed 102 | - [ ] Input validation added where needed 103 | - [ ] Security implications considered 104 | 105 | ### Compatibility 106 | - [ ] Changes are backward compatible (or breaking changes documented) 107 | - [ ] Works with supported Node.js versions 108 | - [ ] Cross-platform compatibility maintained 109 | 110 | ## Deployment Notes 111 | <!-- Any special deployment considerations --> 112 | 113 | - [ ] No special deployment steps required 114 | - [ ] Configuration changes needed (document below) 115 | - [ ] Database migrations required 116 | - [ ] Environment variables added/changed 117 | 118 | <!-- Add deployment notes if needed --> 119 | 120 | ## Reviewer Notes 121 | <!-- Anything specific for reviewers to focus on --> 122 | 123 | ## Additional Context 124 | <!-- Add any other context about the pull request here --> ``` -------------------------------------------------------------------------------- /src/services/tool-context-resolver.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared service for resolving tool contexts 3 | * Maps MCP names to their context types for parameter prediction 4 | */ 5 | 6 | export class ToolContextResolver { 7 | private static readonly contextMap: Record<string, string> = { 8 | 'filesystem': 'filesystem', 9 | 'memory': 'database', 10 | 'shell': 'system', 11 | 'sequential-thinking': 'ai', 12 | 'portel': 'development', 13 | 'tavily': 'web', 14 | 'desktop-commander': 'system', 15 | 'stripe': 'payment', 16 | 'context7-mcp': 'documentation', 17 | 'search': 'search', 18 | 'weather': 'weather', 19 | 'http': 'web', 20 | 'github': 'development', 21 | 'gitlab': 'development', 22 | 'slack': 'communication', 23 | 'discord': 'communication', 24 | 'email': 'communication', 25 | 'database': 'database', 26 | 'redis': 'database', 27 | 'mongodb': 'database', 28 | 'postgresql': 'database', 29 | 'mysql': 'database', 30 | 'elasticsearch': 'search', 31 | 'docker': 'system', 32 | 'kubernetes': 'system', 33 | 'aws': 'cloud', 34 | 'azure': 'cloud', 35 | 'gcp': 'cloud' 36 | }; 37 | 38 | /** 39 | * Get context for a tool based on its full name (mcp:tool format) 40 | */ 41 | static getContext(toolIdentifier: string): string { 42 | const [mcpName] = toolIdentifier.split(':'); 43 | return this.getContextByMCP(mcpName); 44 | } 45 | 46 | /** 47 | * Get context for a specific MCP 48 | */ 49 | static getContextByMCP(mcpName: string): string { 50 | if (!mcpName) return 'general'; 51 | 52 | const normalizedName = mcpName.toLowerCase(); 53 | 54 | // Direct match 55 | if (this.contextMap[normalizedName]) { 56 | return this.contextMap[normalizedName]; 57 | } 58 | 59 | // Partial match for common patterns 60 | if (normalizedName.includes('file') || normalizedName.includes('fs')) { 61 | return 'filesystem'; 62 | } 63 | if (normalizedName.includes('db') || normalizedName.includes('data')) { 64 | return 'database'; 65 | } 66 | if (normalizedName.includes('web') || normalizedName.includes('http')) { 67 | return 'web'; 68 | } 69 | if (normalizedName.includes('api')) { 70 | return 'web'; 71 | } 72 | if (normalizedName.includes('cloud') || normalizedName.includes('aws') || 73 | normalizedName.includes('azure') || normalizedName.includes('gcp')) { 74 | return 'cloud'; 75 | } 76 | if (normalizedName.includes('docker') || normalizedName.includes('container')) { 77 | return 'system'; 78 | } 79 | if (normalizedName.includes('git')) { 80 | return 'development'; 81 | } 82 | 83 | return 'general'; 84 | } 85 | 86 | /** 87 | * Get all known contexts 88 | */ 89 | static getAllContexts(): string[] { 90 | const contexts = new Set(Object.values(this.contextMap)); 91 | contexts.add('general'); 92 | return Array.from(contexts).sort(); 93 | } 94 | 95 | /** 96 | * Check if a context is known 97 | */ 98 | static isKnownContext(context: string): boolean { 99 | return this.getAllContexts().includes(context); 100 | } 101 | 102 | /** 103 | * Add or update a context mapping (for runtime configuration) 104 | */ 105 | static addMapping(mcpName: string, context: string): void { 106 | this.contextMap[mcpName.toLowerCase()] = context; 107 | } 108 | 109 | /** 110 | * Get all MCP names for a specific context 111 | */ 112 | static getMCPsForContext(context: string): string[] { 113 | return Object.entries(this.contextMap) 114 | .filter(([_, ctx]) => ctx === context) 115 | .map(([mcp, _]) => mcp) 116 | .sort(); 117 | } 118 | } ``` -------------------------------------------------------------------------------- /src/utils/updater.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Auto-Update System for @portel/ncp 3 | * 4 | * Checks NPM registry for newer versions and notifies users. 5 | * Follows npm best practices - users control when to update. 6 | */ 7 | 8 | import { logger } from './logger.js'; 9 | import { version as packageVersion, packageName } from './version.js'; 10 | 11 | interface VersionInfo { 12 | current: string; 13 | latest: string; 14 | isOutdated: boolean; 15 | updateCommand: string; 16 | } 17 | 18 | export class NPCUpdater { 19 | private readonly packageName = packageName; 20 | private readonly checkInterval = 24 * 60 * 60 * 1000; // 24 hours 21 | private readonly timeout = 5000; // 5 seconds 22 | 23 | /** 24 | * Get current package version from package.json 25 | */ 26 | private async getCurrentVersion(): Promise<string> { 27 | return packageVersion; 28 | } 29 | 30 | /** 31 | * Fetch latest version from NPM registry 32 | */ 33 | private async getLatestVersion(): Promise<string | null> { 34 | try { 35 | const controller = new AbortController(); 36 | const timeoutId = setTimeout(() => controller.abort(), this.timeout); 37 | 38 | const response = await fetch(`https://registry.npmjs.org/${this.packageName}/latest`, { 39 | signal: controller.signal, 40 | headers: { 41 | 'Accept': 'application/json', 42 | 'User-Agent': 'ncp-updater/1.0.0' 43 | } 44 | }); 45 | 46 | clearTimeout(timeoutId); 47 | 48 | if (!response.ok) { 49 | return null; 50 | } 51 | 52 | const data = await response.json(); 53 | return data.version; 54 | } catch (error) { 55 | // Fail silently - don't disrupt normal operation 56 | logger.debug(`Update check failed: ${error}`); 57 | return null; 58 | } 59 | } 60 | 61 | /** 62 | * Compare version strings (semver-like) 63 | */ 64 | private isNewerVersion(current: string, latest: string): boolean { 65 | const currentParts = current.split('.').map(Number); 66 | const latestParts = latest.split('.').map(Number); 67 | 68 | for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) { 69 | const currentPart = currentParts[i] || 0; 70 | const latestPart = latestParts[i] || 0; 71 | 72 | if (latestPart > currentPart) return true; 73 | if (latestPart < currentPart) return false; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | /** 80 | * Check if update is available 81 | */ 82 | async checkForUpdates(): Promise<VersionInfo | null> { 83 | const current = await this.getCurrentVersion(); 84 | const latest = await this.getLatestVersion(); 85 | 86 | if (!latest) { 87 | return null; // Network/registry error 88 | } 89 | 90 | const isOutdated = this.isNewerVersion(current, latest); 91 | 92 | return { 93 | current, 94 | latest, 95 | isOutdated, 96 | updateCommand: `npm update -g ${this.packageName}` 97 | }; 98 | } 99 | 100 | /** 101 | * Get update tip for --find results (if update available) 102 | */ 103 | async getUpdateTip(): Promise<string | null> { 104 | try { 105 | const versionInfo = await this.checkForUpdates(); 106 | 107 | if (versionInfo?.isOutdated) { 108 | return `🚀 Update available: v${versionInfo.current} → v${versionInfo.latest} (run: ${versionInfo.updateCommand})`; 109 | } 110 | 111 | return null; 112 | } catch (error) { 113 | // Fail silently - updates shouldn't break normal operation 114 | logger.debug(`Update check error: ${error}`); 115 | return null; 116 | } 117 | } 118 | } 119 | 120 | // Singleton instance 121 | export const updater = new NPCUpdater(); ``` -------------------------------------------------------------------------------- /test/mock-mcps/postgres-server.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Mock PostgreSQL MCP Server 5 | * Real MCP server structure with actual tool definitions but mock implementations 6 | * This tests discovery without needing actual PostgreSQL connection 7 | */ 8 | 9 | import { MockMCPServer } from './base-mock-server.js'; 10 | 11 | const serverInfo = { 12 | name: 'postgres-test', 13 | version: '1.0.0', 14 | description: 'PostgreSQL database operations including queries, schema management, and data manipulation' 15 | }; 16 | 17 | const tools = [ 18 | { 19 | name: 'query', 20 | description: 'Execute SQL queries to retrieve data from PostgreSQL database tables. Find records, search data, analyze information.', 21 | inputSchema: { 22 | type: 'object', 23 | properties: { 24 | query: { 25 | type: 'string', 26 | description: 'SQL query string to execute' 27 | }, 28 | params: { 29 | type: 'array', 30 | description: 'Optional parameters for parameterized queries', 31 | items: { 32 | type: 'string' 33 | } 34 | } 35 | }, 36 | required: ['query'] 37 | } 38 | }, 39 | { 40 | name: 'insert', 41 | description: 'Insert new records into PostgreSQL database tables. Store customer data, add new information, create records.', 42 | inputSchema: { 43 | type: 'object', 44 | properties: { 45 | table: { 46 | type: 'string', 47 | description: 'Target table name' 48 | }, 49 | data: { 50 | type: 'object', 51 | description: 'Record data to insert as key-value pairs' 52 | } 53 | }, 54 | required: ['table', 'data'] 55 | } 56 | }, 57 | { 58 | name: 'update', 59 | description: 'Update existing records in PostgreSQL database tables. Modify customer information, change email addresses, edit data.', 60 | inputSchema: { 61 | type: 'object', 62 | properties: { 63 | table: { 64 | type: 'string', 65 | description: 'Target table name' 66 | }, 67 | data: { 68 | type: 'object', 69 | description: 'Updated record data as key-value pairs' 70 | }, 71 | where: { 72 | type: 'string', 73 | description: 'WHERE clause conditions for targeting specific records' 74 | } 75 | }, 76 | required: ['table', 'data', 'where'] 77 | } 78 | }, 79 | { 80 | name: 'delete', 81 | description: 'Delete records from PostgreSQL database tables. Remove old data, clean expired records, purge information.', 82 | inputSchema: { 83 | type: 'object', 84 | properties: { 85 | table: { 86 | type: 'string', 87 | description: 'Target table name' 88 | }, 89 | where: { 90 | type: 'string', 91 | description: 'WHERE clause conditions for targeting records to delete' 92 | } 93 | }, 94 | required: ['table', 'where'] 95 | } 96 | }, 97 | { 98 | name: 'create_table', 99 | description: 'Create new tables in PostgreSQL database with schema definition. Set up user session storage, design tables for customer data.', 100 | inputSchema: { 101 | type: 'object', 102 | properties: { 103 | name: { 104 | type: 'string', 105 | description: 'Table name' 106 | }, 107 | schema: { 108 | type: 'object', 109 | description: 'Table schema definition with columns and types' 110 | } 111 | }, 112 | required: ['name', 'schema'] 113 | } 114 | } 115 | ]; 116 | 117 | // Create and run the server 118 | const server = new MockMCPServer(serverInfo, tools); 119 | server.run().catch(console.error); ```