Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
internal-mcp-manager.ts9.76 kB
/** * Internal MCP Manager * * Manages MCPs that are implemented internally by NCP. * These appear in tool discovery like external MCPs but are handled internally. * * NAMING CONVENTION FOR INTERNAL MCPS: * - MCP Management tools (add, remove, list MCPs) → namespace: 'mcp' * Example: mcp:add, mcp:remove, mcp:list * * - Photon tools (custom user-defined MCPs) → namespace: photon_name * Example: extract:run, get-library-docs:search * * - NCP internal features (analytics, code execution, scheduling, skills) → namespace: 'ncp' * Example: ncp:overview, ncp:run, ncp:create (for scheduled tasks) * Note: Currently individual MCPs use domain-specific names (analytics:, schedule:, skills:) * This is a legacy structure that can be unified under 'ncp' namespace in future refactoring. */ import { InternalMCP, InternalToolResult, ElicitationCapable } from './types.js'; import { NCPManagementMCP } from './ncp-management.js'; import { SchedulerMCP } from './scheduler.js'; import { AnalyticsMCP } from './analytics.js'; import { SkillsManagementMCP } from './skills.js'; import { MarketplaceMCP } from './marketplace.js'; import { CodeMCP } from './code.js'; import { PhotonLoader } from './photon-loader.js'; import ProfileManager from '../profiles/profile-manager.js'; import { logger } from '../utils/logger.js'; import { getNcpBaseDirectory } from '../utils/ncp-paths.js'; import * as path from 'path'; import { fileURLToPath } from 'url'; import * as os from 'os'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class InternalMCPManager { private internalMCPs: Map<string, InternalMCP> = new Map(); private disabledInternalMCPs: Set<string> = new Set(); private ragEngine: any = null; // PersistentRAGEngine instance (set via setRAGEngine) private simpleMCPLoader: PhotonLoader; private codeMCP: CodeMCP; // Keep reference for setting orchestrator later constructor() { this.simpleMCPLoader = new PhotonLoader(); // Register legacy internal MCPs (will be migrated to Photon) this.registerInternalMCP(new NCPManagementMCP()); this.registerInternalMCP(new SchedulerMCP()); this.registerInternalMCP(new AnalyticsMCP()); this.registerInternalMCP(new SkillsManagementMCP()); this.registerInternalMCP(new MarketplaceMCP()); // Register code execution MCP this.codeMCP = new CodeMCP(); this.registerInternalMCP(this.codeMCP); // Note: CLI discovery is internal to orchestrator, not exposed as tools } /** * Load Photon classes from standard directories */ async loadPhotons(): Promise<void> { if (process.env.JEST_WORKER_ID) { logger.info('🧪 Test environment detected - skipping Photon load'); return; } const photonRuntimeEnabled = process.env.NCP_ENABLE_PHOTON_RUNTIME === 'true'; const directories = [ // Built-in Photons (in src/internal-mcps/) __dirname, ]; // Only load user photons if photon runtime is enabled if (photonRuntimeEnabled) { directories.push( // Installed Photons from registry (~/.ncp/photons/) path.join(getNcpBaseDirectory(), 'photons'), // Global user MCPs (~/.ncp/internal/) path.join(getNcpBaseDirectory(), 'internal'), // Project-local MCPs (.ncp/internal/) path.join(process.cwd(), '.ncp', 'internal') ); } logger.debug(`Loading Photons from directories: ${directories.join(', ')}`); if (photonRuntimeEnabled) { logger.info('✅ Photon runtime enabled - loading user photons'); } else { logger.info('ℹ️ Photon runtime disabled - loading built-in photons only'); } const mcps = await this.simpleMCPLoader.loadAll(directories); // Register loaded MCPs for (const mcp of mcps) { this.registerInternalMCP(mcp); } logger.info(`✅ Loaded ${mcps.length} Photon(s)`); } /** * Register an internal MCP */ private registerInternalMCP(mcp: InternalMCP): void { this.internalMCPs.set(mcp.name, mcp); logger.debug(`Registered internal MCP: ${mcp.name}`); } /** * Initialize internal MCPs with ProfileManager */ initialize(profileManager: ProfileManager): void { for (const mcp of this.internalMCPs.values()) { if ('setProfileManager' in mcp && typeof mcp.setProfileManager === 'function') { mcp.setProfileManager(profileManager); } } } /** * Set elicitation server for internal MCPs that support user interaction */ setElicitationServer(server: ElicitationCapable): void { for (const mcp of this.internalMCPs.values()) { if (mcp.setElicitationServer) { mcp.setElicitationServer(server); logger.debug(`Set elicitation server for internal MCP: ${mcp.name}`); } } } /** * Get all internal MCPs for tool discovery */ getAllInternalMCPs(): InternalMCP[] { return Array.from(this.internalMCPs.values()); } /** * Execute a tool from an internal MCP * @param mcpName The internal MCP name (e.g., "ncp") * @param toolName The tool name (e.g., "add") * @param parameters Tool parameters */ async executeInternalTool( mcpName: string, toolName: string, parameters: any ): Promise<InternalToolResult> { const mcp = this.internalMCPs.get(mcpName); if (!mcp) { return { success: false, error: `Internal MCP not found: ${mcpName}` }; } try { return await mcp.executeTool(toolName, parameters); } catch (error: any) { logger.error(`Internal tool execution failed: ${mcpName}:${toolName} - ${error.message}`); return { success: false, error: error.message || 'Internal tool execution failed' }; } } /** * Check if an MCP is internal */ isInternalMCP(mcpName: string): boolean { return this.internalMCPs.has(mcpName); } /** * Get tool names for a specific internal MCP */ getInternalMCPTools(mcpName: string): string[] { const mcp = this.internalMCPs.get(mcpName); return mcp ? mcp.tools.map(t => t.name) : []; } /** * Get capabilities for a specific internal MCP */ getInternalMCPCapabilities(mcpName: string) { const mcp = this.internalMCPs.get(mcpName); return mcp?.capabilities || {}; } /** * Check if an internal MCP has a specific capability * @param mcpName The MCP name (e.g., "scheduler") * @param capabilityPath Dot-notation path (e.g., "experimental.toolValidation.supported") */ hasCapability(mcpName: string, capabilityPath: string): boolean { const mcp = this.internalMCPs.get(mcpName); if (!mcp || !mcp.capabilities) { return false; } // Navigate the capability path const parts = capabilityPath.split('.'); let current: any = mcp.capabilities; for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return false; } } return current === true; } /** * Set the RAG engine instance for managing disabled MCPs */ setRAGEngine(ragEngine: any): void { this.ragEngine = ragEngine; logger.debug('RAG engine connected to InternalMCPManager'); } /** * Set orchestrator on code execution MCP * Called after orchestrator is initialized */ setOrchestratorOnCodeMCP(orchestrator: any): void { this.codeMCP.setOrchestrator(orchestrator); logger.debug('Orchestrator connected to CodeMCP'); } /** * Disable an internal MCP (removes from discovery, triggers re-indexing) */ async disableInternalMCP(mcpName: string): Promise<void> { if (!this.internalMCPs.has(mcpName)) { throw new Error(`Internal MCP not found: ${mcpName}`); } if (this.disabledInternalMCPs.has(mcpName)) { logger.warn(`Internal MCP ${mcpName} is already disabled`); return; } this.disabledInternalMCPs.add(mcpName); logger.info(`🚫 Disabled internal MCP: ${mcpName}`); // Mark as disabled in RAG engine for live filtering if (this.ragEngine) { this.ragEngine.setMCPDisabled(mcpName); // Trigger background re-indexing to rebuild index without disabled MCP await this.ragEngine.triggerBackgroundReindex(); } } /** * Enable an internal MCP (adds to discovery, triggers re-indexing) */ async enableInternalMCP(mcpName: string): Promise<void> { if (!this.internalMCPs.has(mcpName)) { throw new Error(`Internal MCP not found: ${mcpName}`); } if (!this.disabledInternalMCPs.has(mcpName)) { logger.warn(`Internal MCP ${mcpName} is already enabled`); return; } this.disabledInternalMCPs.delete(mcpName); logger.info(`✅ Enabled internal MCP: ${mcpName}`); // Mark as enabled in RAG engine if (this.ragEngine) { this.ragEngine.setMCPEnabled(mcpName); // Trigger background re-indexing to rebuild index with newly enabled MCP // Note: This assumes the MCP tools will be re-indexed when the orchestrator re-indexes await this.ragEngine.triggerBackgroundReindex(); } } /** * Check if an internal MCP is disabled */ isInternalMCPDisabled(mcpName: string): boolean { return this.disabledInternalMCPs.has(mcpName); } /** * Get list of disabled internal MCPs */ getDisabledInternalMCPs(): string[] { return Array.from(this.disabledInternalMCPs); } /** * Get all internal MCPs for tool discovery (excludes disabled MCPs) */ getAllEnabledInternalMCPs(): InternalMCP[] { return Array.from(this.internalMCPs.values()).filter( mcp => !this.disabledInternalMCPs.has(mcp.name) ); } }

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