1212from dbt_mcp .errors import InvalidParameterError , ToolCallError
1313from dbt_mcp .errors .common import NotFoundError
1414from dbt_mcp .gql .errors import raise_gql_error
15- from dbt_mcp .tools .parameters import LineageResourceType
15+ from dbt_mcp .tools .parameters import LineageDirection , LineageResourceType
1616
1717DEFAULT_PAGE_SIZE = 100
1818DEFAULT_MAX_NODE_QUERY_LIMIT = 10000
@@ -819,6 +819,7 @@ async def fetch_lineage(
819819 unique_id : str ,
820820 depth : int ,
821821 types : list [LineageResourceType ] | None = None ,
822+ direction : LineageDirection = LineageDirection .BOTH ,
822823 * ,
823824 config : DiscoveryConfig ,
824825 ) -> list [dict ]:
@@ -828,10 +829,11 @@ async def fetch_lineage(
828829 unique_id: The dbt unique ID of the resource to get lineage for.
829830 depth: how many levels to traverse (0 = infinite, 1 = immediate neighbors only, higher = deeper)
830831 types: List of resource types to include. If None, includes all types.
832+ direction: Which direction(s) to traverse relative to unique_id.
831833 config: Discovery API connection and environment.
832834
833835 Returns:
834- List of nodes connected to unique_id (upstream + downstream) .
836+ List of nodes connected to unique_id, filtered to `direction` .
835837 """
836838 if depth < 0 :
837839 raise ToolCallError ("Depth must be greater than or equal to 0" )
@@ -857,17 +859,22 @@ async def fetch_lineage(
857859 )
858860
859861 # Filter to connected nodes only
860- return self ._filter_connected_nodes (all_nodes , unique_id , depth )
862+ return self ._filter_connected_nodes (all_nodes , unique_id , depth , direction )
861863
862864 def _filter_connected_nodes (
863- self , nodes : list [dict ], target_id : str , depth : int
865+ self ,
866+ nodes : list [dict ],
867+ target_id : str ,
868+ depth : int ,
869+ direction : LineageDirection = LineageDirection .BOTH ,
864870 ) -> list [dict ]:
865- """Return only nodes connected to target_id (upstream and downstream) .
866- Uses BFS to find all nodes reachable from target in both directions .
871+ """Return only nodes connected to target_id, filtered to `direction` .
872+ Uses BFS to find all nodes reachable from target in the requested direction(s) .
867873 Args:
868874 nodes: List of all nodes in the lineage graph.
869875 target_id: The unique ID of the target node.
870876 depth: how many levels to traverse (0 = infinite, 1 = immediate neighbors only, higher = deeper)
877+ direction: Which direction(s) to traverse relative to target_id.
871878 """
872879 node_map = {
873880 n ["uniqueId" ]: n
@@ -882,6 +889,15 @@ def _filter_connected_nodes(
882889 if target_id not in node_map :
883890 return []
884891
892+ include_upstream = direction in (
893+ LineageDirection .UPSTREAM ,
894+ LineageDirection .BOTH ,
895+ )
896+ include_downstream = direction in (
897+ LineageDirection .DOWNSTREAM ,
898+ LineageDirection .BOTH ,
899+ )
900+
885901 # BFS to find all connected nodes
886902 connected = {target_id }
887903 queue = [(target_id , 0 )]
@@ -897,22 +913,24 @@ def _filter_connected_nodes(
897913 continue
898914
899915 # Traverse upstream (parents)
900- for parent_id in node .get ("parentIds" , []):
901- if parent_id not in connected and parent_id in node_map :
902- connected .add (parent_id )
903- queue .append ((parent_id , current_depth + 1 ))
916+ if include_upstream :
917+ for parent_id in node .get ("parentIds" , []):
918+ if parent_id not in connected and parent_id in node_map :
919+ connected .add (parent_id )
920+ queue .append ((parent_id , current_depth + 1 ))
904921
905922 # Traverse downstream (children)
906- for candidate in nodes :
907- candidate_id = candidate .get ("uniqueId" )
908- if not candidate_id or candidate_id not in node_map :
909- continue
910- if (
911- current_id in candidate .get ("parentIds" , [])
912- and candidate_id not in connected
913- ):
914- connected .add (candidate_id )
915- queue .append ((candidate_id , current_depth + 1 ))
923+ if include_downstream :
924+ for candidate in nodes :
925+ candidate_id = candidate .get ("uniqueId" )
926+ if not candidate_id or candidate_id not in node_map :
927+ continue
928+ if (
929+ current_id in candidate .get ("parentIds" , [])
930+ and candidate_id not in connected
931+ ):
932+ connected .add (candidate_id )
933+ queue .append ((candidate_id , current_depth + 1 ))
916934
917935 # Return in original order
918936 return [node_map [uid ] for uid in connected ]
0 commit comments