Skip to main content
Glama
context.ts11.7 kB
/** * Context Tool Meta-Tool * * MCP tool exposing the wpnav context functionality to AI agents. * Provides a compact context dump of the WordPress site, tools, roles, * and cookbooks for token-efficient AI agent initialization. * * @package WP_Navigator_MCP * @since 2.7.0 */ import { toolRegistry, ToolCategory } from '../../tool-registry/index.js'; import type { ToolExecutionContext, ToolResult } from '../../tool-registry/types.js'; import { loadManifest, isManifestV2, getManifestAI, getManifestSafetyV2 } from '../../manifest.js'; import { getFocusMode, getFocusModePreset } from '../../focus-modes.js'; import { discoverCookbooks } from '../../cookbook/index.js'; import { discoverRoles, getRole, type LoadedRole } from '../../roles/index.js'; import { runtimeRoleState } from '../../roles/runtime-state.js'; import type { Tool } from '@modelcontextprotocol/sdk/types.js'; // ============================================================================= // Types // ============================================================================= 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; } // ============================================================================= // Context Building Helpers // ============================================================================= /** * Group enabled tools by category */ function groupToolsByCategory(tools: Tool[]): Record<string, number> { const grouped: Record<string, number> = {}; for (const tool of tools) { 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_') ) { 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'; } else if ( name.includes('search_tools') || name.includes('describe_tools') || name.includes('execute') ) { category = 'meta'; } grouped[category] = (grouped[category] || 0) + 1; } return grouped; } /** * Detect plugins from introspect response */ function detectPlugins(introspect: Record<string, unknown>): string[] { const plugins: string[] = []; const detected = introspect.detected_plugins as string[] | undefined; if (Array.isArray(detected)) { plugins.push(...detected); } 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'], }; } } /** * Build context output from gathered data */ export async function buildContextOutput( context: ToolExecutionContext, options: { compact?: boolean; includeSnapshot?: boolean } = {} ): Promise<ContextOutput> { // 1. Fetch introspect data from WordPress 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()) { 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 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, // Include tool list only if not compact mode ...(!options.compact ? { list: enabledTools.map((t) => t.name) } : {}), }, role: activeRole ? { active: activeRoleSlug, name: activeRole.name, context: !options.compact ? 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, }; return contextOutput; } // ============================================================================= // Tool Definition // ============================================================================= /** * Tool definition for wpnav_context */ export const contextToolDefinition = { name: 'wpnav_context', description: 'Get a compact context dump of the WordPress site including focus mode, available tools, active role, cookbooks, site info, and safety settings. Designed for AI agent initialization.', inputSchema: { type: 'object' as const, properties: { compact: { type: 'boolean', description: 'If true, returns minimal context (~200-300 tokens). If false, includes full tool list and role context.', default: true, }, include_snapshot: { type: 'boolean', description: 'If true, includes a summary of pages and posts. Increases token usage.', default: false, }, }, }, }; // ============================================================================= // Handler // ============================================================================= /** * Handler for wpnav_context * * Returns context dump for AI agent initialization. */ export async function contextToolHandler( args: { compact?: boolean; include_snapshot?: boolean }, context: ToolExecutionContext ): Promise<ToolResult> { const { compact = true, include_snapshot = false } = args; try { const contextOutput = await buildContextOutput(context, { compact, includeSnapshot: include_snapshot, }); return { content: [ { type: 'text', text: JSON.stringify(contextOutput, null, 2), }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: JSON.stringify( { error: 'CONTEXT_FAILED', message: `Failed to gather context: ${errorMessage}`, hint: 'Ensure WordPress site is reachable and WP Navigator plugin is active', }, null, 2 ), }, ], }; } } // ============================================================================= // Registration // ============================================================================= /** * Register the context tool */ export function registerContextTool(): void { toolRegistry.register({ definition: contextToolDefinition, handler: contextToolHandler, category: ToolCategory.CORE, }); }

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