backlog_context
Retrieve comprehensive task context including relationships, references, and activity to understand dependencies before starting work.
Instructions
Get full context for working on a task — parent epic, sibling tasks, children, cross-referenced items, reverse references (who references me), ancestors, descendants, related resources, semantically related items, recent activity, and session memory in a single call. Use this before starting work on any task to understand its context.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| task_id | No | Task or epic ID to get context for. Example: "TASK-0042" or "EPIC-0005". Mutually exclusive with query. | |
| query | No | Natural language query to find the most relevant entity. Example: "search ranking improvements". Mutually exclusive with task_id. | |
| depth | No | Relational expansion depth. 1 = direct relations (default). 2 = grandparent/grandchildren. 3 = three hops. | |
| max_tokens | No | Token budget for the response. Default: 4000. Increase for more detail, decrease for conciseness. | |
| include_related | No | Include semantically related items (default: true). Set false to skip semantic search and reduce latency. | |
| include_activity | No | Include recent activity timeline (default: true). Set false to skip activity and reduce response size. |
Implementation Reference
- Main tool registration and handler function for backlog_context. Defines the tool with input schema validation (task_id, query, depth, max_tokens, include_related, include_activity) and executes the context hydration pipeline via hydrateContext(). Returns formatted JSON response with all context data.
export function registerBacklogContextTool(server: McpServer) { server.registerTool( 'backlog_context', { description: 'Get full context for working on a task — parent epic, sibling tasks, children, cross-referenced items, reverse references (who references me), ancestors, descendants, related resources, semantically related items, recent activity, and session memory in a single call. Use this before starting work on any task to understand its context.', inputSchema: z.object({ task_id: z.string().optional().describe('Task or epic ID to get context for. Example: "TASK-0042" or "EPIC-0005". Mutually exclusive with query.'), query: z.string().optional().describe('Natural language query to find the most relevant entity. Example: "search ranking improvements". Mutually exclusive with task_id.'), depth: z.number().min(1).max(3).optional().describe('Relational expansion depth. 1 = direct relations (default). 2 = grandparent/grandchildren. 3 = three hops.'), max_tokens: z.number().min(500).max(32000).optional().describe('Token budget for the response. Default: 4000. Increase for more detail, decrease for conciseness.'), include_related: z.boolean().optional().describe('Include semantically related items (default: true). Set false to skip semantic search and reduce latency.'), include_activity: z.boolean().optional().describe('Include recent activity timeline (default: true). Set false to skip activity and reduce response size.'), }), }, async ({ task_id, query, depth, max_tokens, include_related, include_activity }) => { if (!task_id && !query) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'Either task_id or query is required' }) }], isError: true, }; } const result = await hydrateContext( { task_id, query, depth: depth ?? 1, max_tokens: max_tokens ?? 4000, include_related: include_related ?? true, include_activity: include_activity ?? true, }, { getTask: (id) => storage.get(id), listTasks: (filter) => storage.listSync(filter), listResources: () => resourceManager.list(), searchUnified: async (q, options) => storage.searchUnified(q, options), readOperations: (options) => operationLogger.read(options), }, ); if (!result) { const target = task_id || query; return { content: [{ type: 'text', text: JSON.stringify({ error: `Entity not found: ${target}` }) }], isError: true, }; } return { content: [{ type: 'text', text: JSON.stringify(formatResponse(result), null, 2) }], }; }, ); } - Zod input schema definition for the backlog_context tool. Validates task_id (optional string), query (optional string, mutually exclusive with task_id), depth (1-3), max_tokens (500-32000), include_related (boolean), and include_activity (boolean).
inputSchema: z.object({ task_id: z.string().optional().describe('Task or epic ID to get context for. Example: "TASK-0042" or "EPIC-0005". Mutually exclusive with query.'), query: z.string().optional().describe('Natural language query to find the most relevant entity. Example: "search ranking improvements". Mutually exclusive with task_id.'), depth: z.number().min(1).max(3).optional().describe('Relational expansion depth. 1 = direct relations (default). 2 = grandparent/grandchildren. 3 = three hops.'), max_tokens: z.number().min(500).max(32000).optional().describe('Token budget for the response. Default: 4000. Increase for more detail, decrease for conciseness.'), include_related: z.boolean().optional().describe('Include semantically related items (default: true). Set false to skip semantic search and reduce latency.'), include_activity: z.boolean().optional().describe('Include recent activity timeline (default: true). Set false to skip activity and reduce response size.'), }), - Type definitions for the context hydration pipeline. Includes ContextRequest (input parameters), ContextResponse (complete output structure with focal, parent, children, siblings, ancestors, descendants, cross_referenced, referenced_by, related_resources, related, activity, session_summary), and supporting types like ContextEntity, ContextResource, ContextActivity, SessionSummary, and ContextMetadata.
export interface ContextRequest { /** Focal entity ID (e.g. TASK-0042, EPIC-0005). Mutually exclusive with query. */ task_id?: string; /** Natural language query to resolve into a focal entity (Phase 2, ADR-0075). */ query?: string; /** Relational expansion depth. 1 = direct relations. Default: 1, max: 3. */ depth?: number; /** Enable semantic enrichment (Stage 3). Default: true for Phase 2. */ include_related?: boolean; /** Enable temporal overlay (Stage 4). Default: true for Phase 2. */ include_activity?: boolean; /** Token budget for the entire response. Default: 4000. */ max_tokens?: number; } // ── Response entities ──────────────────────────────────────────────── export type Fidelity = 'full' | 'summary' | 'reference'; export interface ContextEntity { id: string; title: string; status: Status; type: EntityType; parent_id?: string; fidelity: Fidelity; /** Present when fidelity is 'full' */ description?: string; /** Present when fidelity is 'full' and entity has evidence */ evidence?: string[]; /** Present when fidelity is 'full' and entity has blocked_reason */ blocked_reason?: string[]; /** Present when fidelity is 'full' or 'summary' and entity has references */ references?: { url: string; title?: string }[]; created_at?: string; updated_at?: string; /** Relevance score from semantic search. Present only for semantically discovered entities. */ relevance_score?: number; /** Distance from focal entity in the relational graph (1 = direct, 2 = two hops, etc.). Phase 3, ADR-0076. */ graph_depth?: number; } export interface ContextResource { uri: string; title: string; /** Path relative to data directory */ path: string; fidelity: Fidelity; /** Brief excerpt. Present at 'summary' and 'full' fidelity. */ snippet?: string; /** Full content. Present at 'full' fidelity only. */ content?: string; /** Relevance score from semantic search. Present only for semantically discovered resources. */ relevance_score?: number; } export interface ContextActivity { ts: string; tool: string; entity_id: string; actor: string; summary: string; } // ── Session memory ────────────────────────────────────────────────── /** Summary of the last work session on this entity. Phase 3, ADR-0076. */ export interface SessionSummary { /** Who last worked on this entity */ actor: string; /** Whether the actor was a user or agent */ actor_type: 'user' | 'agent'; /** When the last session started (ISO timestamp) */ started_at: string; /** When the last session ended (ISO timestamp) */ ended_at: string; /** Number of operations in the last session */ operation_count: number; /** Human-readable summary of what was done */ summary: string; } // ── Response ───────────────────────────────────────────────────────── export interface ContextResponse { /** The primary entity the context is built around */ focal: ContextEntity; /** Parent entity (if focal has a parent) */ parent: ContextEntity | null; /** Direct children of the focal entity */ children: ContextEntity[]; /** Siblings (same parent as focal, excluding focal itself) */ siblings: ContextEntity[]; /** Ancestors beyond direct parent (grandparent, great-grandparent). Ordered closest-first. Phase 3, ADR-0076. */ ancestors: ContextEntity[]; /** Descendants beyond direct children (grandchildren, etc.). Phase 3, ADR-0076. */ descendants: ContextEntity[]; /** Entities explicitly referenced by focal's references[] field. Phase 4, ADR-0077. */ cross_referenced: ContextEntity[]; /** Entities whose references[] point to the focal entity (reverse cross-references). Phase 5, ADR-0078. */ referenced_by: ContextEntity[]; /** Resources related to the focal entity or its parent */ related_resources: ContextResource[]; /** Semantically related entities not in the direct graph (Stage 3, ADR-0075). */ related: ContextEntity[]; /** Recent operations on focal and related items (Stage 4, ADR-0075). */ activity: ContextActivity[]; /** Session memory — who last worked on this and what they did (Stage 3.5, ADR-0076). */ session_summary: SessionSummary | null; /** Pipeline execution metadata */ metadata: ContextMetadata; } export interface ContextMetadata { depth: number; total_items: number; /** Estimated token count for the response */ token_estimate: number; /** Whether items were dropped or downgraded to fit the token budget */ truncated: boolean; /** Which pipeline stages were executed */ stages_executed: string[]; /** How the focal entity was resolved: 'id' (direct lookup) or 'query' (search-based). */ focal_resolved_from?: 'id' | 'query'; } - Core hydrateContext function that implements the multi-stage context pipeline. Stages: 1) Focal Resolution (resolve task_id or query to entity), 2) Relational Expansion (parent, children, siblings, ancestors, descendants, resources), 2.5) Cross-Reference Traversal (follow references[] + reverse refs), 3) Semantic Enrichment (find related items via search), 3.5) Session Memory (last work session), 4) Temporal Overlay (recent activity), 5) Token Budgeting (prioritize/truncate to fit budget). Returns complete ContextResponse.
export async function hydrateContext( request: ContextRequest, deps: HydrationServiceDeps, ): Promise<ContextResponse | null> { const maxTokens = request.max_tokens ?? 4000; const depth = Math.min(request.depth ?? 1, 3); const includeRelated = request.include_related ?? true; const includeActivity = request.include_activity ?? true; const stagesExecuted: string[] = []; // ── Stage 1: Focal Resolution ────────────────────────────────── const searchDeps: SearchDeps | undefined = deps.searchUnified ? { search: async (query: string) => { const results = await deps.searchUnified!(query, { types: ['task', 'epic'], limit: 1 }); return results.map(r => ({ item: r.item as Entity, score: r.score })); }, } : undefined; const focalResult = await resolveFocal(request, deps.getTask, searchDeps); if (!focalResult) return null; stagesExecuted.push('focal_resolution'); const { focal, focalTask, resolved_from } = focalResult; // ── Stage 2: Relational Expansion ────────────────────────────── const expansionDeps: RelationalExpansionDeps = { getTask: deps.getTask, listTasks: deps.listTasks, listResources: deps.listResources, }; const expansion = expandRelations(focalTask, depth, expansionDeps); stagesExecuted.push('relational_expansion'); // ── Stage 2.5: Cross-Reference Traversal (Phase 4+5, ADR-0077+0078) ── // Build a visited set from Stages 1-2 output for dedup const visited = new Set<string>([focal.id]); if (expansion.parent) visited.add(expansion.parent.id); for (const c of expansion.children) visited.add(c.id); for (const s of expansion.siblings) visited.add(s.id); for (const a of expansion.ancestors) visited.add(a.id); for (const d of expansion.descendants) visited.add(d.id); // Resolve the parent Task for reference collection (parent is summary fidelity // from Stage 2, but we need the raw Task for its references field) const parentTask = expansion.parent ? deps.getTask(expansion.parent.id) ?? null : null; const crossRefResult = traverseCrossReferences( focalTask, parentTask, visited, // Mutated: resolved cross-ref IDs are added { getTask: deps.getTask, listTasks: deps.listTasks }, ); if (crossRefResult.cross_referenced.length > 0 || crossRefResult.referenced_by.length > 0) { stagesExecuted.push('cross_reference_traversal'); } // ── Stage 3: Semantic Enrichment ─────────────────────────────── let semanticEntities: ContextResponse['related'] = []; let semanticResources: ContextResponse['related_resources'] = []; if (includeRelated && deps.searchUnified) { // Reuse the visited set (now includes cross-referenced + referenced_by IDs) for dedup const existingIds = visited; const existingResourceUris = new Set<string>( expansion.related_resources.map(r => r.uri), ); const enrichment = await enrichSemantic( focalTask, existingIds, existingResourceUris, { searchUnified: deps.searchUnified }, ); semanticEntities = enrichment.related_entities; semanticResources = enrichment.related_resources; stagesExecuted.push('semantic_enrichment'); } // ── Stage 3.5: Session Memory ────────────────────────────────── let sessionSummary: SessionSummary | null = null; if (deps.readOperations) { const sessionDeps: SessionMemoryDeps = { readOperations: deps.readOperations, }; sessionSummary = deriveSessionSummary(focal.id, sessionDeps); if (sessionSummary) { stagesExecuted.push('session_memory'); } } // ── Stage 4: Temporal Overlay ────────────────────────────────── let activity: ContextResponse['activity'] = []; if (includeActivity && deps.readOperations) { // Query activity for focal + parent + children (focused set) const activityEntityIds = [focal.id]; if (expansion.parent) activityEntityIds.push(expansion.parent.id); for (const c of expansion.children) activityEntityIds.push(c.id); activity = overlayTemporal( activityEntityIds, { readOperations: deps.readOperations }, 20, ); stagesExecuted.push('temporal_overlay'); } // ── Stage 5: Token Budgeting ─────────────────────────────────── // Combine path-matched and semantic resources for budgeting const allResources = [...expansion.related_resources, ...semanticResources]; const budget = applyBudget( focal, expansion.parent, expansion.children, expansion.siblings, crossRefResult.cross_referenced, crossRefResult.referenced_by, expansion.ancestors, expansion.descendants, semanticEntities, allResources, activity, sessionSummary, maxTokens, ); stagesExecuted.push('token_budgeting'); // Separate budget entities back into their roles const budgetedFocal = budget.entities[0]!; let idx = 1; let budgetedParent: ContextResponse['parent'] = null; if (expansion.parent && idx < budget.entities.length) { const candidate = budget.entities[idx]!; if (candidate.id === expansion.parent.id) { budgetedParent = candidate; idx++; } } // Use sets for role separation const childIds = new Set(expansion.children.map(c => c.id)); const siblingIds = new Set(expansion.siblings.map(s => s.id)); const crossRefIds = new Set(crossRefResult.cross_referenced.map(x => x.id)); const referencedByIds = new Set(crossRefResult.referenced_by.map(r => r.id)); const ancestorIds = new Set(expansion.ancestors.map(a => a.id)); const descendantIds = new Set(expansion.descendants.map(d => d.id)); const semanticIds = new Set(semanticEntities.map(r => r.id)); const budgetedChildren: ContextResponse['children'] = []; const budgetedSiblings: ContextResponse['siblings'] = []; const budgetedCrossReferenced: ContextResponse['cross_referenced'] = []; const budgetedReferencedBy: ContextResponse['referenced_by'] = []; const budgetedAncestors: ContextResponse['ancestors'] = []; const budgetedDescendants: ContextResponse['descendants'] = []; const budgetedRelated: ContextResponse['related'] = []; for (let i = idx; i < budget.entities.length; i++) { const e = budget.entities[i]!; if (childIds.has(e.id)) { budgetedChildren.push(e); } else if (siblingIds.has(e.id)) { budgetedSiblings.push(e); } else if (crossRefIds.has(e.id)) { budgetedCrossReferenced.push(e); } else if (referencedByIds.has(e.id)) { budgetedReferencedBy.push(e); } else if (ancestorIds.has(e.id)) { budgetedAncestors.push(e); } else if (descendantIds.has(e.id)) { budgetedDescendants.push(e); } else if (semanticIds.has(e.id)) { budgetedRelated.push(e); } } const totalItems = 1 + // focal (budgetedParent ? 1 : 0) + budgetedChildren.length + budgetedSiblings.length + budgetedCrossReferenced.length + budgetedReferencedBy.length + budgetedAncestors.length + budgetedDescendants.length + budget.resources.length + budgetedRelated.length + budget.activities.length + (budget.sessionSummary ? 1 : 0); return { focal: budgetedFocal, parent: budgetedParent, children: budgetedChildren, siblings: budgetedSiblings, cross_referenced: budgetedCrossReferenced, referenced_by: budgetedReferencedBy, ancestors: budgetedAncestors, descendants: budgetedDescendants, related_resources: budget.resources, related: budgetedRelated, activity: budget.activities, session_summary: budget.sessionSummary, metadata: { depth, total_items: totalItems, token_estimate: budget.tokensUsed, truncated: budget.truncated, stages_executed: stagesExecuted, focal_resolved_from: resolved_from, }, }; } - packages/server/src/tools/index.ts:8-18 (registration)Tool registration module that imports registerBacklogContextTool and calls it within the registerTools function to register all backlog tools including backlog_context with the MCP server.
import { registerBacklogContextTool } from './backlog-context.js'; export function registerTools(server: McpServer) { registerBacklogListTool(server); registerBacklogGetTool(server); registerBacklogCreateTool(server); registerBacklogUpdateTool(server); registerBacklogDeleteTool(server); registerBacklogSearchTool(server); registerBacklogContextTool(server); }