Blender 2.8 Python : How Do I Find My Material Output Node (and assign displacement)
The Material Output can be retrieved by name or by type. If you know the name of the node and the variable nodes contains a reference to the nodes of the material's node tree, then you can access the Material Output node through a look up where the name is used as key:

material_output = nodes.get("Material Output")


A material may contain more than one Material Output node or it may have been renamed. In this case you could search for a node that is of type OUTPUT_MATERIAL.

material_output = None
for node in nodes:
    if node.type == "OUTPUT_MATERIAL":
        material_output = node
        break


In both cases it may happen that no Material Output node exists, for instance because you're working on an existing material where it was deleted. Therefore you need to check if material_output is None and if it is create a new node.

if material_output is None:
    material_output = nodes.new("ShaderNodeOutputMaterial")


When you're creating a new material from scratch you do know that both the Principled BSDF and Material Output nodes exist and you can access them by name.



In order to connect two nodes you need access to the material's node tree links. Assuming the variable links references these, you can add new links through the new() function. The first argument is the input socket of a node and the second argument is the output socket of a node. 
Assuming that principled_bsdf would reference a Principled BSDF node and base_color an Image Texture node, you could connect them in the following way:

links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"]) 




The following code shows how to create your desired node setup by creating a new material which contains a Principled BSDF and Material Output node by default.

import bpy

# Define names and paths
material_name = "Example_Material"
base_color_path = "" # Add your path here
normal_map_path = "" # Add your path here

# Create a material
material = bpy.data.materials.new(name=material_name)
material.use_nodes = True
nodes = material.node_tree.nodes
links = material.node_tree.links

# Since you want a Principled BSDF and the Material Output node
# in your material, we can re-use the nodes that are automatically
# created.
principled_bsdf = nodes.get("Principled BSDF")
material_output = nodes.get("Material Output")

# Create Image Texture node and load the base color texture
base_color = nodes.new('ShaderNodeTexImage')
base_color.image = bpy.data.images.load(base_color_path)

# Create Image Texture node and load the normal map
normal_tex = nodes.new('ShaderNodeTexImage')
normal_tex.image = bpy.data.images.load(normal_map_path)

# Set the color space to non-color, since normal maps contain
# the direction of the surface normals and not color data
normal_tex.image.colorspace_settings.name = "Non-Color"

# Create the Displacement node
displacement = nodes.new('ShaderNodeDisplacement')

# Connect the base color texture to the Principled BSDF
links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"])

# Connect the normal map to the Displacement node
links.new(displacement.inputs["Height"], normal_tex.outputs["Color"])

# Connect the Displacement node to the Material Output node
links.new(material_output.inputs["Displacement"], displacement.outputs["Displacement"])




The following example is for editing an existing material:

import bpy

# Define names and paths
material_name = "Example_Material"
base_color_path = "" # Add your path here
normal_map_path = "" # Add your path here

# In case you're modifying an existing material you
# will have to check whether or not it contains a
# Material Output node. If it doesn't it has to be
# created.
material = bpy.data.materials[material_name]
material.use_nodes = True
nodes = material.node_tree.nodes
links = material.node_tree.links

# In case you don't know the name of the nodes you can also search 
# by type. This can be useful when you edit an existing material 
# where the nodes could've been renamed.

# Find the Material Output node, if it exists
material_output = None
for node in nodes:
    if node.type == "OUTPUT_MATERIAL":
        material_output = node
        break

# Perhaps the nodes hasn't been found then you'll have to create it
if material_output is None:
    material_output = nodes.new("ShaderNodeOutputMaterial")

# Create a Principled BSDF nodes
principled_bsdf = nodes.new("ShaderNodeBsdfPrincipled")

# Create Image Texture node and load the base color texture
base_color = nodes.new('ShaderNodeTexImage')
base_color.image = bpy.data.images.load(base_color_path)

# Create Image Texture node and load the normal map
normal_tex = nodes.new('ShaderNodeTexImage')
normal_tex.image = bpy.data.images.load(normal_map_path)

# Set the color space to non-color, since normal maps contain
# the direction of the surface normals and not color data
normal_tex.image.colorspace_settings.name = "Non-Color"

# Create the Displacement node
displacement = nodes.new('ShaderNodeDisplacement')

# Connect the base color texture to the Principled BSDF
links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"])

# Connect the normal map to the Displacement node
links.new(displacement.inputs["Height"], normal_tex.outputs["Color"])

# Connect the Principled BSDF node to the Material Output node
links.new(material_output.inputs["Surface"], principled_bsdf.outputs["BSDF"])

# Connect the Displacement node to the Material Output node
links.new(material_output.inputs["Displacement"], displacement.outputs["Displacement"])


If you want to assign the material to the active object in Blender 2.8x then the the following line can be added to either of the scripts above. We do not need to retrieve the material again, since we already have a reference to it in the variable material.

bpy.context.active_object.active_material = material