Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
mcp-prompts.ts10.2 kB
/** * MCP Prompts for User Interaction * * Uses MCP protocol's prompts capability to request user input/approval * Works with Claude Desktop and other MCP clients that support prompts */ export interface PromptMessage { role: 'user' | 'assistant'; content: { type: 'text' | 'image'; text?: string; data?: string; mimeType?: string; }; } export interface Prompt { name: string; description?: string; arguments?: Array<{ name: string; description?: string; required?: boolean; }>; } /** * Available prompts for NCP management operations */ export const NCP_PROMPTS: Prompt[] = [ { name: 'confirm_add_mcp', description: 'Request user confirmation before adding a new MCP server', arguments: [ { name: 'mcp_name', description: 'Name of the MCP server to add', required: true }, { name: 'command', description: 'Command to execute', required: true }, { name: 'profile', description: 'Target profile name', required: false } ] }, { name: 'confirm_remove_mcp', description: 'Request user confirmation before removing an MCP server', arguments: [ { name: 'mcp_name', description: 'Name of the MCP server to remove', required: true }, { name: 'profile', description: 'Profile to remove from', required: false } ] }, { name: 'configure_mcp', description: 'Request user input for MCP configuration (env vars, args)', arguments: [ { name: 'mcp_name', description: 'Name of the MCP being configured', required: true }, { name: 'config_type', description: 'Type of configuration needed', required: true } ] }, { name: 'approve_dangerous_operation', description: 'Request approval for potentially dangerous operations', arguments: [ { name: 'operation', description: 'Description of the operation', required: true }, { name: 'impact', description: 'Potential impact description', required: true } ] }, { name: 'confirm_operation', description: 'Request user confirmation before executing modifier operations', arguments: [ { name: 'tool', description: 'Tool identifier (mcp:tool format)', required: true }, { name: 'tool_description', description: 'Description of what the tool does', required: false }, { name: 'parameters', description: 'JSON string of parameters being passed', required: false }, { name: 'matched_pattern', description: 'The modifier pattern that matched', required: false }, { name: 'confidence', description: 'Match confidence (0.0-1.0)', required: false } ] } ]; /** * Generate prompt message for MCP add confirmation * * SECURITY: Supports clipboard-based secret configuration! * User can copy config with API keys to clipboard BEFORE clicking YES. * NCP reads clipboard server-side - secrets NEVER exposed to AI. */ export function generateAddConfirmation( mcpName: string, command: string, args: string[], profile: string = 'all' ): PromptMessage[] { const argsStr = args.length > 0 ? ` ${args.join(' ')}` : ''; return [ { role: 'user', content: { type: 'text', text: `Do you want to add the MCP server "${mcpName}" to profile "${profile}"?\n\nCommand: ${command}${argsStr}\n\nThis will allow Claude to access the tools provided by this MCP server.\n\n📋 SECURE SETUP (Optional):\nTo include API keys/tokens WITHOUT exposing them to this conversation:\n1. Copy your config to clipboard BEFORE clicking YES\n2. Example: {"env":{"API_KEY":"your_secret_here"}}\n3. Click YES - NCP will read from clipboard\n\nOr click YES without copying for basic setup.` } }, { role: 'assistant', content: { type: 'text', text: 'Please respond with YES to confirm or NO to cancel.' } } ]; } /** * Generate prompt message for MCP remove confirmation */ export function generateRemoveConfirmation( mcpName: string, profile: string = 'all' ): PromptMessage[] { return [ { role: 'user', content: { type: 'text', text: `Do you want to remove the MCP server "${mcpName}" from profile "${profile}"?\n\nThis will remove access to all tools provided by this MCP server.` } }, { role: 'assistant', content: { type: 'text', text: 'Please respond with YES to confirm or NO to cancel.' } } ]; } /** * Generate prompt message for configuration input */ export function generateConfigInput( mcpName: string, configType: string, description: string ): PromptMessage[] { return [ { role: 'user', content: { type: 'text', text: `Configuration needed for "${mcpName}":\n\n${description}\n\nPlease provide the required value.` } } ]; } /** * Generate prompt message for operation confirmation (confirm-before-run feature) * * Shows rich dialog with tool description, parameters, and reason for confirmation. * User can approve once, approve always (whitelist), or cancel. */ export function generateOperationConfirmation( toolIdentifier: string, toolDescription: string, parameters: Record<string, any>, matchedPattern: string, confidence: number ): PromptMessage[] { const [mcpName, toolName] = toolIdentifier.split(':'); // Format parameters nicely let parametersText = ''; if (Object.keys(parameters).length > 0) { parametersText = '\n\nParameters:'; for (const [key, value] of Object.entries(parameters)) { const valueStr = typeof value === 'string' ? value : JSON.stringify(value, null, 2); parametersText += `\n ${key}: ${valueStr}`; } } else { parametersText = '\n\nParameters: (none)'; } const confidencePercent = Math.round(confidence * 100); return [ { role: 'user', content: { type: 'text', text: `⚠️ CONFIRMATION REQUIRED Tool: ${toolName} MCP: ${mcpName} Description: ${toolDescription || 'No description available'}${parametersText} Reason: Matches modifier pattern (${confidencePercent}% confidence) Pattern: "${matchedPattern}" This operation may modify data or have side effects. Do you want to proceed? - Reply "YES" to approve this once - Reply "ALWAYS" to approve and add to whitelist (won't ask again) - Reply "NO" to cancel` } }, { role: 'assistant', content: { type: 'text', text: 'Please respond with YES (once), ALWAYS (whitelist), or NO (cancel).' } } ]; } /** * Parse user response from prompt */ export function parseConfirmationResponse(response: string): boolean { const normalized = response.trim().toLowerCase(); return normalized === 'yes' || normalized === 'y' || normalized === 'confirm'; } /** * Parse operation confirmation response * Returns: 'once' | 'always' | 'cancel' */ export function parseOperationConfirmationResponse(response: string): 'once' | 'always' | 'cancel' { const normalized = response.trim().toLowerCase(); if (normalized === 'always' || normalized === 'whitelist') { return 'always'; } if (normalized === 'yes' || normalized === 'y' || normalized === 'approve' || normalized === 'confirm') { return 'once'; } return 'cancel'; } /** * Parse configuration input response */ export function parseConfigResponse(response: string): string { return response.trim(); } /** * Try to read and parse clipboard content for MCP configuration * Returns additional config (env vars, args) from clipboard or null if invalid * * SECURITY: This is called AFTER user clicks YES on prompt that tells them * to copy config first. It's explicit user consent, not sneaky background reading. */ export async function tryReadClipboardConfig(): Promise<{ env?: Record<string, string>; args?: string[]; } | null> { try { // Dynamically import clipboardy to avoid loading in all contexts const clipboardy = await import('clipboardy'); const clipboardContent = await clipboardy.default.read(); if (!clipboardContent || clipboardContent.trim().length === 0) { return null; // Empty clipboard - user didn't copy anything } // Try to parse as JSON try { const config = JSON.parse(clipboardContent.trim()); // Validate it's an object with expected properties if (typeof config !== 'object' || config === null) { return null; } // Extract only env and args (ignore other fields for security) const result: { env?: Record<string, string>; args?: string[] } = {}; if (config.env && typeof config.env === 'object') { result.env = config.env; } if (Array.isArray(config.args)) { result.args = config.args; } // Only return if we found something useful if (result.env || result.args) { return result; } return null; } catch (parseError) { // Not valid JSON - user didn't copy config return null; } } catch (error) { // Clipboard access failed - not critical, just return null return null; } } /** * Merge base config with clipboard config * Clipboard config takes precedence for env vars and can add additional args */ export function mergeWithClipboardConfig( baseConfig: { command: string; args?: string[]; env?: Record<string, string>; }, clipboardConfig: { env?: Record<string, string>; args?: string[]; } | null ): { command: string; args?: string[]; env?: Record<string, string>; } { if (!clipboardConfig) { return baseConfig; } return { command: baseConfig.command, args: clipboardConfig.args || baseConfig.args, env: { ...(baseConfig.env || {}), ...(clipboardConfig.env || {}) // Clipboard env vars override base } }; }

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