search
Search plans, nodes, and content within the planning system using queries and filters to find specific project information.
Instructions
Universal search tool for plans, nodes, and content
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| scope | No | Search scope | global |
| scope_id | No | Plan ID (if scope is 'plan') or Node ID (if scope is 'node') | |
| query | Yes | Search query | |
| filters | No | Optional filters |
Implementation Reference
- src/tools.js:60-104 (schema)Schema definition for the 'search' tool, specifying input parameters including scope, query, scope_id, and filters.{ name: "search", description: "Universal search tool for plans, nodes, and content", inputSchema: { type: "object", properties: { scope: { type: "string", description: "Search scope", enum: ["global", "plans", "plan", "node"], default: "global" }, scope_id: { type: "string", description: "Plan ID (if scope is 'plan') or Node ID (if scope is 'node')" }, query: { type: "string", description: "Search query" }, filters: { type: "object", description: "Optional filters", properties: { status: { type: "string", description: "Filter by status", enum: ["draft", "active", "completed", "archived", "not_started", "in_progress", "blocked"] }, type: { type: "string", description: "Filter by type", enum: ["plan", "node", "phase", "task", "milestone", "artifact", "log"] }, limit: { type: "integer", description: "Maximum number of results", default: 20 } } } }, required: ["query"] } },
- src/tools.js:443-517 (handler)Primary handler for executing the 'search' tool. Dispatches based on scope: client-side search for 'plans', delegates to search-wrapper for 'global' and 'plan', placeholder for 'node'.if (name === "search") { const { scope, scope_id, query, filters = {} } = args; let results = []; switch (scope) { case "global": // Global search across all plans const searchWrapper = require('./tools/search-wrapper'); results = await searchWrapper.globalSearch(query); break; case "plans": // Search only in plan titles/descriptions const plans = await apiClient.plans.getPlans(); // Handle wildcard queries if (query === '*' || query === '' || !query) { // Return all plans (with optional status filter) results = plans.filter(plan => !filters.status || plan.status === filters.status ); } else { // Normal search const queryLower = query.toLowerCase(); results = plans.filter(plan => { const titleMatch = plan.title.toLowerCase().includes(queryLower); const descMatch = plan.description?.toLowerCase().includes(queryLower); const statusMatch = !filters.status || plan.status === filters.status; return (titleMatch || descMatch) && statusMatch; }); } break; case "plan": // Search within a specific plan if (!scope_id) { throw new Error("scope_id (plan_id) is required when scope is 'plan'"); } const searchWrapperPlan = require('./tools/search-wrapper'); results = await searchWrapperPlan.searchPlan(scope_id, query); break; case "node": // Search within a specific node's children if (!scope_id) { throw new Error("scope_id (node_id) is required when scope is 'node'"); } // This would need a specific implementation results = []; break; default: // Default to global search const searchWrapperDefault = require('./tools/search-wrapper'); results = await searchWrapperDefault.globalSearch(query); } // Apply filters if (filters.type) { results = results.filter(item => item.type === filters.type); } if (filters.limit) { results = results.slice(0, filters.limit); } return formatResponse({ query, scope, scope_id, filters, count: results.length, results }); }
- src/tools/search-wrapper.js:79-182 (helper)Helper function globalSearch that wraps apiClient.search.globalSearch, normalizes various response formats into a unified array of results.async function globalSearch(query) { try { // Call the original global search function const response = await apiClient.search.globalSearch(query); if (process.env.NODE_ENV === 'development') { console.log('Global search response type:', typeof response); if (response) { console.log('Response keys:', Object.keys(response)); } } // Handle different response formats if (!response) { return []; } // If response is already an array of results if (Array.isArray(response)) { return response.map(item => ({ ...item, type: item.type || 'unknown', source: 'global_search' })); } // If response has a results property if (response.results !== undefined) { // If results is already an array, return it if (Array.isArray(response.results)) { return response.results.map(item => ({ ...item, type: item.type || 'unknown', source: 'global_search' })); } // If results is an object with categories if (typeof response.results === 'object') { const allResults = []; // Collect results from each category (plans, nodes, comments, etc.) Object.keys(response.results).forEach(category => { if (Array.isArray(response.results[category])) { // Add category type to each result const categoryResults = response.results[category].map(item => ({ ...item, type: item.type || category, category, source: 'global_search' })); allResults.push(...categoryResults); } }); return allResults; } } // Check if response has categorized results (plans, nodes, etc.) const categories = ['plans', 'nodes', 'comments', 'logs', 'artifacts']; const allResults = []; categories.forEach(category => { if (response[category] && Array.isArray(response[category])) { response[category].forEach(item => { allResults.push({ ...item, type: item.type || category.slice(0, -1), // Remove 's' from category category, source: 'global_search' }); }); } }); if (allResults.length > 0) { return allResults; } // Generic handler for any object with arrays Object.keys(response).forEach(key => { if (Array.isArray(response[key]) && key !== 'results') { response[key].forEach(item => { allResults.push({ ...item, type: item.type || key, category: key, source: 'global_search' }); }); } }); return allResults; } catch (error) { console.error('Error in globalSearch wrapper:', error.message); if (error.response) { console.error('API error status:', error.response.status); console.error('API error data:', error.response.data); } return []; } }
- src/tools/search-wrapper.js:17-71 (helper)Helper function searchPlan that wraps apiClient.search.searchPlan, normalizes response to return results array.async function searchPlan(planId, query) { try { // Call the original search function from the API client const response = await apiClient.search.searchPlan(planId, query); // Log the actual response for debugging if (process.env.NODE_ENV === 'development') { console.log('Search plan response:', typeof response, Object.keys(response || {})); } // Handle different response formats if (!response) { return []; } // If response is already an array, return it if (Array.isArray(response)) { return response; } // If response has results array if (response.results && Array.isArray(response.results)) { return response.results; } // If response has other properties that are arrays const results = []; Object.keys(response).forEach(key => { if (Array.isArray(response[key])) { response[key].forEach(item => { results.push({ ...item, type: item.type || key, source: 'plan_search' }); }); } }); if (results.length > 0) { return results; } // Try to parse response as a search result object if (response.query !== undefined && response.count !== undefined) { return response.results || []; } console.error('Unexpected search response format:', JSON.stringify(response).substring(0, 200)); return []; } catch (error) { console.error('Error in searchPlan wrapper:', error.message); return []; } }
- src/tools.js:56-430 (registration)Registration of all tools including 'search' via setRequestHandler for ListToolsRequestSchema.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // ===== UNIFIED SEARCH TOOL ===== { name: "search", description: "Universal search tool for plans, nodes, and content", inputSchema: { type: "object", properties: { scope: { type: "string", description: "Search scope", enum: ["global", "plans", "plan", "node"], default: "global" }, scope_id: { type: "string", description: "Plan ID (if scope is 'plan') or Node ID (if scope is 'node')" }, query: { type: "string", description: "Search query" }, filters: { type: "object", description: "Optional filters", properties: { status: { type: "string", description: "Filter by status", enum: ["draft", "active", "completed", "archived", "not_started", "in_progress", "blocked"] }, type: { type: "string", description: "Filter by type", enum: ["plan", "node", "phase", "task", "milestone", "artifact", "log"] }, limit: { type: "integer", description: "Maximum number of results", default: 20 } } } }, required: ["query"] } }, // ===== PLAN MANAGEMENT TOOLS ===== { name: "list_plans", description: "List all plans or filter by status", inputSchema: { type: "object", properties: { status: { type: "string", description: "Optional filter by plan status", enum: ["draft", "active", "completed", "archived"] } } } }, { name: "create_plan", description: "Create a new plan", inputSchema: { type: "object", properties: { title: { type: "string", description: "Plan title" }, description: { type: "string", description: "Plan description" }, status: { type: "string", description: "Plan status", enum: ["draft", "active", "completed", "archived"], default: "draft" } }, required: ["title"] } }, { name: "update_plan", description: "Update an existing plan", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, title: { type: "string", description: "New plan title" }, description: { type: "string", description: "New plan description" }, status: { type: "string", description: "New plan status", enum: ["draft", "active", "completed", "archived"] } }, required: ["plan_id"] } }, { name: "delete_plan", description: "Delete a plan", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID to delete" } }, required: ["plan_id"] } }, // ===== NODE MANAGEMENT TOOLS ===== { name: "create_node", description: "Create a new node in a plan", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" }, node_type: { type: "string", description: "Node type", enum: ["phase", "task", "milestone"] }, title: { type: "string", description: "Node title" }, description: { type: "string", description: "Node description" }, status: { type: "string", description: "Node status", enum: ["not_started", "in_progress", "completed", "blocked"], default: "not_started" }, context: { type: "string", description: "Additional context for the node" }, agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" }, acceptance_criteria: { type: "string", description: "Criteria for node completion" }, due_date: { type: "string", description: "Due date (ISO format)" }, metadata: { type: "object", description: "Additional metadata" } }, required: ["plan_id", "node_type", "title"] } }, { name: "update_node", description: "Update a node's properties", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" }, title: { type: "string", description: "New node title" }, description: { type: "string", description: "New node description" }, status: { type: "string", description: "New node status", enum: ["not_started", "in_progress", "completed", "blocked"] }, context: { type: "string", description: "New context" }, agent_instructions: { type: "string", description: "New agent instructions" }, acceptance_criteria: { type: "string", description: "New acceptance criteria" }, due_date: { type: "string", description: "New due date (ISO format)" }, metadata: { type: "object", description: "New metadata" } }, required: ["plan_id", "node_id"] } }, { name: "delete_node", description: "Delete a node and all its children", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID to delete" } }, required: ["plan_id", "node_id"] } }, { name: "move_node", description: "Move a node to a different parent or position", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID to move" }, parent_id: { type: "string", description: "New parent node ID" }, order_index: { type: "integer", description: "New position index" } }, required: ["plan_id", "node_id"] } }, { name: "get_node_context", description: "Get comprehensive context for a node including children, logs, and artifacts", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" } }, required: ["plan_id", "node_id"] } }, { name: "get_node_ancestry", description: "Get the path from root to a specific node", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" } }, required: ["plan_id", "node_id"] } }, // ===== LOGGING TOOLS (Replaces Comments) ===== { name: "add_log", description: "Add a log entry to a node (replaces comments)", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" }, content: { type: "string", description: "Log content" }, log_type: { type: "string", description: "Type of log entry", enum: ["progress", "reasoning", "challenge", "decision", "comment"], default: "comment" }, tags: { type: "array", description: "Tags for categorizing the log entry", items: { type: "string" } } }, required: ["plan_id", "node_id", "content"] } }, { name: "get_logs", description: "Get log entries for a node", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" }, log_type: { type: "string", description: "Filter by log type", enum: ["progress", "reasoning", "challenge", "decision", "comment"] }, limit: { type: "integer", description: "Maximum number of logs to return", default: 50 } }, required: ["plan_id", "node_id"] } }, // ===== ARTIFACT MANAGEMENT ===== { name: "manage_artifact", description: "Add, get, or search for artifacts", inputSchema: { type: "object", properties: { action: { type: "string", description: "Action to perform", enum: ["add", "get", "search", "list"] }, plan_id: { type: "string", description: "Plan ID" }, node_id: { type: "string", description: "Node ID" }, artifact_id: { type: "string", description: "Artifact ID (for 'get' action)" }, name: { type: "string", description: "Artifact name (for 'add' or 'search')" }, content_type: { type: "string", description: "Content MIME type (for 'add')" }, url: { type: "string", description: "URL where artifact can be accessed (for 'add')" }, metadata: { type: "object", description: "Additional metadata (for 'add')" } }, required: ["action", "plan_id", "node_id"] } }, // ===== BATCH OPERATIONS ===== { name: "batch_update_nodes", description: "Update multiple nodes at once", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, updates: { type: "array", description: "List of node updates", items: { type: "object", properties: { node_id: { type: "string", description: "Node ID" }, status: { type: "string", enum: ["not_started", "in_progress", "completed", "blocked"] }, title: { type: "string" }, description: { type: "string" } }, required: ["node_id"] } } }, required: ["plan_id", "updates"] } }, { name: "batch_get_artifacts", description: "Get multiple artifacts at once", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, artifact_requests: { type: "array", description: "List of artifact requests", items: { type: "object", properties: { node_id: { type: "string", description: "Node ID" }, artifact_id: { type: "string", description: "Artifact ID" } }, required: ["node_id", "artifact_id"] } } }, required: ["plan_id", "artifact_requests"] } }, // ===== PLAN STRUCTURE & SUMMARY ===== { name: "get_plan_structure", description: "Get the complete hierarchical structure of a plan", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" }, include_details: { type: "boolean", description: "Include full node details", default: false } }, required: ["plan_id"] } }, { name: "get_plan_summary", description: "Get a comprehensive summary with statistics", inputSchema: { type: "object", properties: { plan_id: { type: "string", description: "Plan ID" } }, required: ["plan_id"] } } ] }; });