Skip to main content
Glama
context.ts14.6 kB
/** * WP Navigator Context Command * * CLI command for outputting AI-consumable context for web-based AI agents * that cannot use MCP protocol (Claude Code Web, Codex Cloud, etc.). * * @package WP_Navigator_MCP * @since 2.7.0 */ import { success, error as errorMessage, info, newline, box, keyValue, colorize, symbols, } from '../tui/components.js'; import { toolRegistry } from '../../tool-registry/index.js'; import { loadManifest, isManifestV2, getManifestTools, getManifestAI, getManifestSafetyV2, } from '../../manifest.js'; import { getFocusMode, getFocusModePreset, resolveFocusMode, mergeFocusModeWithManifest, } from '../../focus-modes.js'; import { discoverRoles, getRole, type LoadedRole } from '../../roles/index.js'; import { runtimeRoleState } from '../../roles/runtime-state.js'; import { discoverCookbooks } from '../../cookbook/index.js'; import type { WPConfig } from '../../config.js'; import type { Tool } from '@modelcontextprotocol/sdk/types.js'; // ============================================================================= // Types // ============================================================================= export interface ContextCommandOptions { json?: boolean; format?: 'compact' | 'full'; includeTools?: boolean; includeRole?: boolean; includeCookbooks?: boolean; } export interface ContextOutput { focus_mode: { name: string; description: string; token_estimate: string; }; tools: { total_available: number; enabled: number; by_category: Record<string, number>; list?: string[]; }; role: { active: string | null; name: string | null; context: string | null; focus_areas: string[]; tools_allowed: string[]; tools_denied: string[]; } | null; cookbooks: { loaded: string[]; available: string[]; recommended: string[]; }; site: { name: string | null; url: string; plugin_version: string | null; plugin_edition: string | null; detected_plugins: string[]; page_builder: string | null; }; safety: { mode: string; enable_writes: boolean; allowed_operations: string[]; blocked_operations: string[]; }; ai: { instructions: string | null; prompts_path: string | null; }; environment: string; } // ============================================================================= // Output Helpers // ============================================================================= function outputJSON(data: unknown): void { console.log(JSON.stringify(data, null, 2)); } // ============================================================================= // Context Gathering Functions // ============================================================================= /** * Group enabled tools by category */ function groupToolsByCategory(tools: Tool[]): Record<string, number> { const grouped: Record<string, number> = {}; for (const tool of tools) { // Tool names start with wpnav_ followed by category verb // e.g. wpnav_list_posts -> content, wpnav_introspect -> core const name = tool.name; let category = 'other'; if ( name.startsWith('wpnav_list_') || name.startsWith('wpnav_get_') || name.startsWith('wpnav_create_') || name.startsWith('wpnav_update_') || name.startsWith('wpnav_delete_') ) { // Extract category from the noun if ( name.includes('_post') || name.includes('_page') || name.includes('_media') || name.includes('_comment') ) { category = 'content'; } else if (name.includes('_categor') || name.includes('_tag') || name.includes('_taxonom')) { category = 'taxonomy'; } else if (name.includes('_user')) { category = 'users'; } else if (name.includes('_plugin')) { category = 'plugins'; } else if (name.includes('_theme')) { category = 'themes'; } else if (name.includes('_cookbook') || name.includes('_role')) { category = 'ai'; } } else if (name.includes('introspect') || name.includes('help') || name.includes('overview')) { category = 'core'; } else if (name.includes('gutenberg') || name.includes('block')) { category = 'gutenberg'; } else if (name.includes('batch')) { category = 'batch'; } grouped[category] = (grouped[category] || 0) + 1; } return grouped; } /** * Detect plugins from introspect response */ function detectPlugins(introspect: Record<string, unknown>): string[] { const plugins: string[] = []; // Check for detected plugins in introspect response const detected = introspect.detected_plugins as string[] | undefined; if (Array.isArray(detected)) { plugins.push(...detected); } // Check for page builder const pageBuilder = introspect.page_builder as string | undefined; if (pageBuilder && !plugins.includes(pageBuilder)) { plugins.push(pageBuilder); } return plugins; } /** * Get safety operations based on mode */ function getSafetyOperations(mode: string): { allowed: string[]; blocked: string[] } { switch (mode) { case 'yolo': return { allowed: ['create', 'update', 'delete', 'activate', 'deactivate', 'batch'], blocked: [], }; case 'cautious': return { allowed: ['create', 'update'], blocked: ['delete', 'activate', 'deactivate', 'batch'], }; case 'normal': default: return { allowed: ['create', 'update', 'delete'], blocked: ['batch'], }; } } // ============================================================================= // Command Handler // ============================================================================= /** * Handle `wpnav context` command * * Gathers all AI context from config, manifest, tools, roles, and cookbooks. * Requires WordPress connection to ensure accurate, complete context. */ export async function handleContext( options: ContextCommandOptions = {}, context?: { config: WPConfig; // wpRequest returns parsed JSON directly, not a Response object wpRequest: (endpoint: string, init?: RequestInit) => Promise<unknown>; } ): Promise<number> { // Require connection context if (!context) { if (options.json) { outputJSON({ success: false, command: 'context', error: { code: 'CONNECTION_REQUIRED', message: 'WordPress connection required. Run wpnav context with a valid config.', }, }); } else { errorMessage('WordPress connection required.'); newline(); info( 'The context command needs to connect to your WordPress site to gather accurate information.' ); info('Ensure you have a valid wpnav.config.json or .wpnav.env file.'); } return 1; } try { // 1. Fetch introspect data from WordPress // Note: wpRequest returns parsed JSON directly, not a Response object const introspect = (await context.wpRequest('/wpnav/v1/introspect')) as Record<string, unknown>; // 2. Load manifest const manifestResult = loadManifest(); const manifest = manifestResult.found && manifestResult.manifest && isManifestV2(manifestResult.manifest) ? manifestResult.manifest : null; // 3. Get focus mode const focusMode = manifest ? getFocusMode(manifest) : 'content-editing'; const focusModePreset = getFocusModePreset(focusMode); // 4. Get enabled tools const allTools = toolRegistry.getAllDefinitions(); const enabledTools = allTools.filter((t) => toolRegistry.isEnabled(t.name)); const toolsByCategory = groupToolsByCategory(enabledTools); // 5. Get active role const activeRoleSlug = runtimeRoleState.getRole(); let activeRole: LoadedRole | null = null; if (activeRoleSlug) { activeRole = getRole(activeRoleSlug) || null; } // 6. Get cookbooks const { cookbooks: availableCookbooks } = discoverCookbooks(); const loadedCookbooks: string[] = []; const recommendedCookbooks: string[] = []; // Check for detected plugins that have matching cookbooks const detectedPlugins = detectPlugins(introspect); for (const cookbook of availableCookbooks.values()) { // Check if cookbook matches detected plugins const cookbookSlug = cookbook.plugin.slug.toLowerCase(); const cookbookPluginName = cookbook.plugin.name.toLowerCase(); for (const plugin of detectedPlugins) { const pluginLower = plugin.toLowerCase(); if ( pluginLower.includes(cookbookSlug) || cookbookSlug.includes(pluginLower) || pluginLower.includes(cookbookPluginName) || cookbookPluginName.includes(pluginLower) ) { recommendedCookbooks.push(cookbook.plugin.name); break; } } } // 7. Get safety settings const safety = manifest ? getManifestSafetyV2(manifest) : null; const safetyMode = safety?.mode || 'normal'; const safetyOps = getSafetyOperations(safetyMode); // 8. Get AI settings const ai = manifest ? getManifestAI(manifest) : null; // 9. Determine environment (v2.7.0 multi-env will add this, default for now) const environment = 'production'; // Build context output const contextOutput: ContextOutput = { focus_mode: { name: focusMode, description: focusModePreset.description, token_estimate: focusModePreset.tokenEstimate, }, tools: { total_available: allTools.length, enabled: enabledTools.length, by_category: toolsByCategory, ...(options.includeTools || options.format === 'full' ? { list: enabledTools.map((t) => t.name) } : {}), }, role: activeRole ? { active: activeRoleSlug, name: activeRole.name, context: options.includeRole || options.format === 'full' ? activeRole.context : null, focus_areas: activeRole.focus_areas || [], tools_allowed: activeRole.tools?.allowed || [], tools_denied: activeRole.tools?.denied || [], } : null, cookbooks: { loaded: loadedCookbooks, available: Array.from(availableCookbooks.keys()), recommended: recommendedCookbooks, }, site: { name: (introspect.site_name as string) || null, url: context.config.baseUrl, plugin_version: (introspect.plugin_version as string) || (introspect.version as string) || null, plugin_edition: (introspect.edition as string) || null, detected_plugins: detectedPlugins, page_builder: (introspect.page_builder as string) || null, }, safety: { mode: safetyMode, enable_writes: context.config.toggles.enableWrites, allowed_operations: safetyOps.allowed, blocked_operations: safetyOps.blocked, }, ai: { instructions: ai?.instructions || null, prompts_path: ai?.prompts_path || null, }, environment, }; // Output based on format if (options.json || options.format === 'compact') { outputJSON({ success: true, command: 'context', data: contextOutput, }); return 0; } // TUI output newline(); box('WP Navigator Context', { title: 'AI Agent Context' }); newline(); // Focus Mode section info('Focus Mode'); keyValue(' Mode', focusMode); keyValue(' Description', focusModePreset.description); keyValue(' Token Estimate', focusModePreset.tokenEstimate); newline(); // Tools section info('Tools'); keyValue(' Total Available', String(allTools.length)); keyValue(' Enabled', String(enabledTools.length)); for (const [category, count] of Object.entries(toolsByCategory)) { keyValue(` ${category}`, String(count)); } newline(); // Role section info('Active Role'); if (activeRole) { keyValue(' Slug', activeRoleSlug || '-'); keyValue(' Name', activeRole.name); keyValue(' Description', activeRole.description); if (activeRole.focus_areas && activeRole.focus_areas.length > 0) { info(' Focus Areas:'); for (const area of activeRole.focus_areas.slice(0, 5)) { console.error(` ${colorize(symbols.success, 'green')} ${area}`); } } } else { console.error(` ${colorize('No role active', 'dim')}`); } newline(); // Cookbooks section info('Cookbooks'); keyValue(' Available', String(availableCookbooks.size)); if (recommendedCookbooks.length > 0) { keyValue(' Recommended', recommendedCookbooks.join(', ')); } newline(); // Site section info('Site'); keyValue(' Name', contextOutput.site.name || '-'); keyValue(' URL', contextOutput.site.url); keyValue(' Plugin Version', contextOutput.site.plugin_version || '-'); keyValue(' Plugin Edition', contextOutput.site.plugin_edition || 'Free'); if (detectedPlugins.length > 0) { keyValue(' Detected Plugins', detectedPlugins.join(', ')); } if (contextOutput.site.page_builder) { keyValue(' Page Builder', contextOutput.site.page_builder); } newline(); // Safety section info('Safety'); keyValue(' Mode', safetyMode); keyValue(' Writes Enabled', context.config.toggles.enableWrites ? 'Yes' : 'No'); keyValue(' Allowed', safetyOps.allowed.join(', ')); if (safetyOps.blocked.length > 0) { keyValue(' Blocked', safetyOps.blocked.join(', ')); } newline(); // Environment keyValue('Environment', environment); newline(); // Hint for JSON output info(`Use ${colorize('--json', 'cyan')} for machine-readable output`); info( `Use ${colorize('--format full', 'cyan')} for complete details including tool list and role context` ); return 0; } catch (error) { if (options.json) { outputJSON({ success: false, command: 'context', error: { code: 'CONNECTION_FAILED', message: error instanceof Error ? error.message : 'Failed to gather context', }, }); } else { errorMessage(error instanceof Error ? error.message : 'Failed to gather context'); newline(); info('Ensure your WordPress site is reachable and WP Navigator plugin is active.'); } return 1; } } export default handleContext;

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/littlebearapps/wp-navigator-mcp'

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