dbt-graph
Traverse dbt's parent and child maps to return upstream and downstream nodes (models, sources, tests) up to a given depth.
Instructions
Walk dbt parent_map / child_map to return upstream and downstream nodes (model/source/test) up to a given depth
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uniqueId | No | dbt unique_id | |
| name | No | Model name (resolved if uniqueId not provided) | |
| upstreamDepth | No | ||
| downstreamDepth | No |
Implementation Reference
- src/tools/dbt-models.ts:137-180 (handler)The main handler function for the 'dbt-graph' tool. It loads the manifest, resolves the starting node by uniqueId or name, then walks parent_map (upstream) and child_map (downstream) BFS-style up to configurable depth. Returns start node info, upstream nodes with depth, and downstream nodes with depth.
export async function dbtGraph(args: z.infer<typeof dbtGraphSchema>): Promise<unknown> { const manifest = loadManifest(); let startId = args.uniqueId; if (!startId && args.name) { const found = Object.values(manifest.nodes).find((n) => n.name === args.name); startId = found?.unique_id; } if (!startId) throw new Error(`Node not found: ${args.uniqueId ?? args.name}`); const parent = manifest.parent_map ?? {}; const child = manifest.child_map ?? {}; function walk(map: Record<string, string[]>, id: string, depth: number): Map<string, number> { const out = new Map<string, number>(); const queue: Array<[string, number]> = [[id, 0]]; while (queue.length) { const [cur, d] = queue.shift()!; if (d >= depth) continue; for (const next of map[cur] ?? []) { if (out.has(next)) continue; out.set(next, d + 1); queue.push([next, d + 1]); } } return out; } function describe(id: string): { uniqueId: string; name: string; resourceType: string } { const node = manifest.nodes[id] ?? manifest.sources[id] ?? manifest.macros[id]; return { uniqueId: id, name: node?.name ?? id.split(".").pop() ?? id, resourceType: node?.resource_type ?? "unknown", }; } const upstream = walk(parent, startId, args.upstreamDepth); const downstream = walk(child, startId, args.downstreamDepth); return { start: describe(startId), upstream: Array.from(upstream).map(([id, depth]) => ({ ...describe(id), depth })), downstream: Array.from(downstream).map(([id, depth]) => ({ ...describe(id), depth })), }; } - src/tools/dbt-models.ts:130-135 (schema)Zod schema for dbt-graph input validation: uniqueId (optional), name (optional fallback for resolution), upstreamDepth (default 2, max 10), downstreamDepth (default 2, max 10).
export const dbtGraphSchema = z.object({ uniqueId: z.string().optional().describe("dbt unique_id"), name: z.string().optional().describe("Model name (resolved if uniqueId not provided)"), upstreamDepth: z.coerce.number().int().min(0).max(10).default(2), downstreamDepth: z.coerce.number().int().min(0).max(10).default(2), }); - src/index.ts:91-91 (registration)Registration of the 'dbt-graph' tool with the MCP server via tool(). Name, description, schema shape, and wrapped handler are provided.
tool("dbt-graph", "Walk dbt parent_map / child_map to return upstream and downstream nodes (model/source/test) up to a given depth", dbtGraphSchema.shape, wrapToolHandler(dbtGraph)); - src/tools/dbt-models.ts:164-171 (helper)Helper function 'describe' that resolves a unique_id to its name and resource_type from manifest.nodes, manifest.sources, or manifest.macros.
function describe(id: string): { uniqueId: string; name: string; resourceType: string } { const node = manifest.nodes[id] ?? manifest.sources[id] ?? manifest.macros[id]; return { uniqueId: id, name: node?.name ?? id.split(".").pop() ?? id, resourceType: node?.resource_type ?? "unknown", }; } - src/tools/dbt-models.ts:149-162 (helper)Helper function 'walk' that performs BFS traversal on a parent_map/child_map to find connected nodes up to a given depth.
function walk(map: Record<string, string[]>, id: string, depth: number): Map<string, number> { const out = new Map<string, number>(); const queue: Array<[string, number]> = [[id, 0]]; while (queue.length) { const [cur, d] = queue.shift()!; if (d >= depth) continue; for (const next of map[cur] ?? []) { if (out.has(next)) continue; out.set(next, d + 1); queue.push([next, d + 1]); } } return out; }