Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
config-prompter.ts•6.9 kB
/** * Configuration Prompter * * Interactively prompts users for configuration values based on configurationSchema * Used during `ncp add` and `ncp repair` to guide users through setup */ import prompts from 'prompts'; import chalk from 'chalk'; import { ConfigurationSchema, ConfigurationParameter } from './config-schema-reader.js'; import { redactIfSensitive } from '../utils/redact-sensitive.js'; export interface ConfigValues { environmentVariables: Record<string, string>; arguments: string[]; other: Record<string, string>; } export class ConfigPrompter { /** * Interactively prompt for all required configuration */ async promptForConfig(schema: ConfigurationSchema, mcpName: string): Promise<ConfigValues> { const config: ConfigValues = { environmentVariables: {}, arguments: [], other: {} }; console.log(chalk.blue(`\nđź“‹ Configuration needed for ${mcpName}:\n`)); // Prompt for environment variables if (schema.environmentVariables && schema.environmentVariables.length > 0) { console.log(chalk.bold('Environment Variables:')); for (const param of schema.environmentVariables) { if (param.required) { const value = await this.promptForParameter(param, 'env'); if (value !== null) { config.environmentVariables[param.name] = value; } } } console.log(''); } // Prompt for command arguments if (schema.arguments && schema.arguments.length > 0) { console.log(chalk.bold('Command Arguments:')); for (const param of schema.arguments) { if (param.required) { if (param.multiple) { const values = await this.promptForMultipleValues(param); config.arguments.push(...values); } else { const value = await this.promptForParameter(param, 'arg'); if (value !== null) { config.arguments.push(value); } } } } console.log(''); } // Prompt for other configuration if (schema.other && schema.other.length > 0) { console.log(chalk.bold('Other Configuration:')); for (const param of schema.other) { if (param.required) { const value = await this.promptForParameter(param, 'other'); if (value !== null) { config.other[param.name] = value; } } } } return config; } /** * Prompt for a single parameter */ private async promptForParameter( param: ConfigurationParameter, category: 'env' | 'arg' | 'other' ): Promise<string | null> { const message = this.buildPromptMessage(param); const response = await prompts({ type: this.getPromptType(param), name: 'value', message, initial: param.default, validate: (value: any) => this.validateParameter(value, param) }); if (response.value === undefined) { return null; // User cancelled } return String(response.value); } /** * Prompt for multiple values (for parameters with multiple: true) */ private async promptForMultipleValues(param: ConfigurationParameter): Promise<string[]> { const values: string[] = []; let addMore = true; while (addMore) { const message = values.length === 0 ? this.buildPromptMessage(param) : `Add another ${param.name}?`; const response = await prompts({ type: this.getPromptType(param), name: 'value', message, validate: (value: any) => this.validateParameter(value, param) }); if (response.value === undefined || response.value === '') { break; // User cancelled or entered empty } values.push(String(response.value)); // Ask if they want to add more if (values.length > 0) { const continueResponse = await prompts({ type: 'confirm', name: 'continue', message: `Add another ${param.name}?`, initial: false }); addMore = continueResponse.continue === true; } } return values; } /** * Build prompt message with description and examples */ private buildPromptMessage(param: ConfigurationParameter): string { let message = chalk.cyan(`${param.name}:`); if (param.description) { message += chalk.dim(`\n ${param.description}`); } if (param.examples && param.examples.length > 0 && !param.sensitive) { message += chalk.dim(`\n Examples: ${param.examples.join(', ')}`); } if (param.required) { message += chalk.red(' (required)'); } return message; } /** * Get appropriate prompts type based on parameter type */ private getPromptType(param: ConfigurationParameter): 'text' | 'password' | 'confirm' | 'number' { if (param.sensitive) { return 'password'; } switch (param.type) { case 'boolean': return 'confirm'; case 'number': return 'number'; case 'path': case 'url': case 'string': default: return 'text'; } } /** * Validate parameter value */ private validateParameter(value: any, param: ConfigurationParameter): boolean | string { // Required check if (param.required && (value === undefined || value === null || value === '')) { return `${param.name} is required`; } // Type validation if (param.type === 'number' && isNaN(Number(value))) { return `${param.name} must be a number`; } // Pattern validation if (param.pattern && typeof value === 'string') { const regex = new RegExp(param.pattern); if (!regex.test(value)) { return `${param.name} must match pattern: ${param.pattern}`; } } return true; } /** * Display configuration summary before saving */ displaySummary(config: ConfigValues, mcpName: string): void { console.log(chalk.green.bold(`\nâś“ Configuration for ${mcpName}:\n`)); if (Object.keys(config.environmentVariables).length > 0) { console.log(chalk.bold('Environment Variables:')); Object.entries(config.environmentVariables).forEach(([key, value]) => { // Mask sensitive values using centralized utility const displayValue = redactIfSensitive(key, value); console.log(chalk.dim(` ${key}=${displayValue}`)); }); console.log(''); } if (config.arguments.length > 0) { console.log(chalk.bold('Command Arguments:')); config.arguments.forEach(arg => { console.log(chalk.dim(` ${arg}`)); }); console.log(''); } if (Object.keys(config.other).length > 0) { console.log(chalk.bold('Other Configuration:')); Object.entries(config.other).forEach(([key, value]) => { console.log(chalk.dim(` ${key}: ${value}`)); }); } } }

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