Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
CLAUDE.md14.4 kB
## PRE-COMMIT CHECKLIST - MANDATORY FOR ALL CHANGES Before committing ANY changes to server code, verify ALL of these: - [ ] Did I run the test suite? (`npm run test`) - [ ] Did I test both CLI and DXT entry points? (`npm run test:integration` AND `npm run test:integration:dxt`) - [ ] Did all integration tests pass? - [ ] Did I run the comprehensive DXT test? (`npm run build && node tests/integration/comprehensive-dxt-test.cjs`) - [ ] Did all 5 comprehensive tests pass? - [ ] If I added a feature, did I add tests for it? - [ ] If I'm touching `src/index-mcp.ts`, did I verify `await server.run()` is present? - [ ] If I'm adding MCP protocol handlers, did I test with `notifications/initialized`? **If ANY checkbox is unchecked, DO NOT COMMIT. Fix the issue first.** ## Server Architecture - Single SDK-Based Implementation **We use ONE official SDK-based server implementation:** ### Server Implementation - `src/server/mcp-server.ts` (MCPServer) - Uses official @modelcontextprotocol/sdk - Used by CLI entry point (dist/index.js) - Used by DXT entry point (dist/index-mcp.js) - Fully async initialization with background indexing - Immediate protocol responses (no blocking) ### Why We Use the Official SDK **Historical Context**: We previously had TWO implementations (custom MCPServer and SDK-based MCPServerSDK) which caused recurring bugs: - Features added to one but forgotten in the other - Tests only covered CLI entry point, missing DXT bugs - Every release had bugs discovered by users in production **Solution**: Eliminated duplication by using ONLY the official @modelcontextprotocol/sdk: - Single source of truth for all MCP protocol handling - Official SDK ensures protocol compliance - No feature divergence possible - All tests cover the same implementation ## MCP Protocol Requirements - **NEVER block the event loop**: MCP clients (Claude Desktop, Cursor) will timeout and reject connections if protocol responses are delayed - **Background initialization is mandatory**: Server must respond to protocol handshakes immediately, run heavy initialization in background promises - **NO synchronous file operations**: Always use async file APIs (`readFile`, `writeFile`, `access`) instead of sync versions (`readFileSync`, `writeFileSync`, `existsSync`) - **Cache operations must be async**: Any disk I/O during initialization must not block - use `await` consistently - **Test with production scenarios**: If building for MCP server mode, test as MCP server, not just CLI - **ALWAYS await async initialization**: src/index-mcp.ts MUST await server.run() - wrap in async IIFE if needed: ```typescript (async () => { const server = new MCPServer(profileName); await server.run(); // REQUIRED - process exits without await })(); ``` - **Send notifications/initialized**: After initialize response, send notification to trigger oninitialized callback: ```javascript // After receiving initialize response test.sendNotification('notifications/initialized', {}); ``` ## Performance & Optimization - **Incremental caching is critical**: When one MCP changes, only re-index that MCP, not all 37 - **Track what actually changed**: Use sets/maps to track newly indexed items, only save those to cache - **Avoid redundant writes**: Before writing to cache, check if data actually changed - **Profile hash comparison**: Use config hashes to detect changes, not timestamps ## Caching Architecture - **Dual cache system**: CSV cache for fast loading, metadata cache for schemas - **Per-MCP patching**: Use `patchAddMCP()` for individual MCP updates, not full cache rewrites - **Schema preservation**: Tool schemas MUST be saved to metadata cache during indexing - **Cache migration**: Detect missing metadata and auto-trigger re-indexing for backward compatibility - **Metadata validation**: Always check if schemas exist before loading from cache ## Testing & Debugging ### Test Coverage Requirements **CRITICAL**: Tests MUST match production environment 1. **Test BOTH entry points**: ```bash npm run test:integration # CLI entry (dist/index.js) npm run test:integration:dxt # DXT entry (dist/index-mcp.js) ``` 2. **Comprehensive DXT test** (tests/integration/comprehensive-dxt-test.cjs): - ✅ Server initialization with clientInfo - ✅ Auto-import detects and imports extensions - ✅ Tool discovery (find) works - ✅ Tool execution (run) works - ✅ Multiple sequential requests without crashes 3. **Use correct profile paths in tests**: - DXT uses local `.ncp/` directory (process.cwd()) - CLI uses global `~/.ncp/` directory - Tests must match the environment they're testing 4. **Follow MCP protocol completely**: ```javascript // 1. Send initialize request const id = test.sendRequest('initialize', { protocolVersion: '2024-11-05', clientInfo: { name: 'claude-desktop', version: '0.14.0' } }); // 2. Wait for response await test.waitForResponse(id); // 3. Send initialized notification (REQUIRED to trigger oninitialized) test.sendNotification('notifications/initialized', {}); ``` ### General Testing Rules - **Test all CLI commands**: When asked to test, actually run every command listed (find, run, list, etc.) - **Test with actual installations**: Use `npm link` and test from different directories - **No hardcoded fallbacks**: Fallback values hide real errors - fail fast to surface issues - **Verify schema display**: Tool discovery showing `[no parameters]` means schemas weren't saved - **Check logs**: When debugging, grep for specific operations like "Patching tool metadata" or "Saving.*MCP" - **Test with JSON-RPC directly**: Don't involve AI in tests - use direct JSON-RPC communication - **Verify auto-import**: Check that profile file is updated with new MCPs after initialization ## Code Execution Architecture ### How Code Execution Works (Critical for Understanding NCP) **IMPORTANT**: Code execution is NOT just "display code results". It actually EXECUTES TypeScript with full MCP access via Worker Threads. ### Unified Code Execution Path Both the `code` tool (direct code-mode) and `code:run` tool (scheduled jobs) use the **same mechanism**: ``` User executes code ↓ orchestrator.executeCode(code, timeout) ↓ CodeExecutor.executeCode() ↓ Creates Worker Thread with: - Code to execute - All 70+ MCP tools (from toolsProvider) - Security bindings ↓ Worker creates namespaces: - schedule.* (for scheduling) - ncp.* (for discovery) - analytics.* (for analytics) - mail.* (for email) - github.* (for GitHub) - ... all other MCPs ... ↓ Code executes with full MCP access ↓ Results returned ``` ### MCP Namespace Injection (VERIFIED WORKING) When code executes, **all MCPs are available as callable namespaces**: ```typescript // All of these work in code execution: const jobs = await schedule.list({ limit: 10 }); const tools = await ncp.find({ description: "email" }); const overview = await analytics.overview({}); const mails = await mail({ operation: "unread" }); // Can orchestrate multiple MCPs in one execution: const emails = await mail({ operation: "list", limit: 5 }); const notes = await notes({ operation: "create", text: "..." }); const msg = await messages({ operation: "send", number: "..." }); ``` ### Key Characteristics of Code Execution 1. **Worker Thread Isolation**: Code runs in isolated Worker thread with resource limits - Memory: 128MB max - Execution time: 30s default, 5min max - Network: Controlled via NetworkPolicyManager 2. **MCP Namespace Access**: All enabled MCPs available as namespaces - Tools passed from `toolsProvider()` which includes: - External MCPs (from ~/.ncp/config.json) - Internal MCPs (schedule, analytics, skills, code, ncp) - Skill tools (from ~/.ncp/skills/) 3. **Tool Executor Callback**: When code calls an MCP method, messages are sent to main thread - Tool call: Worker → Main thread - Tool result: Main thread → Worker - Synchronous from code perspective (via Promise handling) 4. **Security Hardening**: - Dangerous patterns blocked (eval, require, import, etc.) - Built-in prototypes frozen - Direct filesystem/process access blocked - Network requests routed through policy enforcement ### Code Tool vs code:run Tool | Aspect | `code` tool (direct) | `code:run` (scheduled) | |--------|----------------------|----------------------| | Entry point | MCP `tools/call` handler | Internal MCP tool | | Execution | `orchestrator.executeCode()` | `orchestrator.executeCode()` | | MCP Access | ✅ Full namespace injection | ✅ Full namespace injection | | Return Format | Formatted text for Claude | JSON result | | Use Case | Direct in Claude chat | Scheduled job automation | | Difference | **NONE** - Same mechanism | | **This is critical**: When designing scheduled jobs with `code:run`, assume MCPs are available as namespaces. They will be. ### Testing Code Execution When testing code execution: 1. **Don't test for "no namespace injection"** - it WILL be injected 2. **Test that MCPs are callable** - verify methods execute 3. **Test error handling** - what happens when a method fails 4. **Test multi-MCP orchestration** - do multiple calls work together Example test: ```typescript const code = ` const schedule_result = await schedule.list({ limit: 1 }); const ncp_result = await ncp.find({ description: "test" }); const analytics_result = await analytics.overview({}); return { schedule_works: !!schedule_result, ncp_works: !!ncp_result, analytics_works: !!analytics_result }; `; // This will return { schedule_works: true, ncp_works: true, analytics_works: true } ``` ## Code Quality & Naming Conventions ### TypeScript/JavaScript Naming Standards **Best Practice: Use camelCase consistently throughout the entire codebase** (code AND configuration files) - **Variables, functions, methods**: `camelCase` (e.g., `enableCodeMode`, `autoImport`, `maxDebugFiles`) - **Classes, interfaces, types**: `PascalCase` (e.g., `GlobalSettings`, `MCPServer`, `InternalMCP`) - **Constants**: `UPPER_CASE` (e.g., `DEFAULT_SETTINGS`, `MAX_RETRIES`) - **JSON configuration**: `camelCase` for consistency with TypeScript (e.g., manifest.json, settings.json) **Key Principle**: NEVER mix camelCase and snake_case in the same codebase. Consistency prevents confusion and makes code maintainable. JavaScript/TypeScript systems standardize on camelCase (even for JSON configuration files). **Application to NCP**: - manifest.json: Use camelCase for all config keys - ✅ `enableCodeMode` (not `enable_code_mode`) - ✅ `enableSkills` (not `enable_skills`) - ✅ `enableScheduleMcp` (not `enable_schedule_mcp`) - ✅ `enablePhotonRuntime` (not `enable_photon_runtime`) - Environment variables: Use UPPER_SNAKE_CASE (follows convention for env vars) - `NCP_ENABLE_CODE_MODE`, `NCP_ENABLE_SKILLS`, etc. - TypeScript interfaces/code: Use camelCase - GlobalSettings.enableCodeMode - GlobalSettings.enableSkills **References**: - [TypeScript ESLint Naming Convention Rule](https://typescript-eslint.io/rules/naming-convention/) - [TypeScript Deep Dive Style Guide](https://basarat.gitbook.io/typescript/styleguide) - [JSON Naming Conventions Stack Overflow](https://stackoverflow.com/questions/5543490/json-naming-convention-snake-case-camelcase-or-pascalcase) - [JSON Best Practices Blog](https://blog.liquid-technologies.com/json-best-practices-and-conventions-part-2-of-4) ### General Code Quality - **Complete the root cause fix**: Don't just fix symptoms, understand and fix the underlying issue - **Verify incremental changes work**: When optimizing caching, test that partial updates work correctly - **Old cache migration**: New features must handle users with old cache format gracefully - **Document assumptions**: If code assumes something (like all MCPs have schemas), validate it ## DXT Build Process **CRITICAL**: Always use `npm run build:dxt` which runs `scripts/build-dxt-clean.sh`. Never use manual build steps. ### Known Issue: mcpb excludes build/ directories - **Problem**: `@anthropic-ai/mcpb` bundler excludes `build/` directories from node_modules by default - **Impact**: Dependencies like `human-signals` require `build/src/main.js` to work - missing causes immediate crash - **Symptoms**: "ERR_MODULE_NOT_FOUND: Cannot find module '.../build/src/main.js'" in logs - **Root Cause**: mcpb treats build/ as build artifacts to exclude, even when they're published package code - **Solution**: `build-dxt-clean.sh` automatically patches missing build directories after mcpb packing ### Build Process Steps (automated in scripts/build-dxt-clean.sh) 1. Build TypeScript (`npm run build`) 2. Create clean temp directory with production files only 3. Fresh `npm install --omit=dev` in clean directory (no mixed dev/prod dependencies) 4. Pack with mcpb (creates initial zip) 5. **Workaround**: Extract DXT (unzip), manually add missing build directories, re-pack (zip) 6. **Critical**: Use `unzip`/`zip` commands - DXT is ZIP format, NOT tar.gz! 7. **Auto-test**: Verify zip format, dependencies, and test server startup 8. Output size and SHA256 hash for verification ### DXT Testing Requirements - **Dependency verification**: Check critical build directories exist (human-signals/build) - **MCP spec timing**: Initialize < 5000ms, tools/list < 2000ms (tested automatically) - **No crashes**: Server must stay up during initialize → tools/list sequence - **Test unpacked DXT**: Always unpack and test before distributing to catch packaging issues early ### Error Handling in Production - **Global error handlers**: `index-mcp.ts` has uncaughtException and unhandledRejection handlers - **stderr logging**: All errors log to stderr with `[NCP FATAL]` or `[NCP ERROR]` prefix - **Stack traces**: Full stack traces included for debugging in Claude Desktop logs - **Explicit exit**: Process exits with code 1 on fatal errors (no silent failures) ## Release Process - **No release until user confirms**: Build and test, but wait for explicit confirmation before releasing - **Test incrementally**: After each fix, rebuild and verify it works before moving to next issue - **Always use build:dxt**: Never manually run mcpb - use the tested build script

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/portel-dev/ncp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server