diff --git a/bin/list_local_saml_idp_endpoints.sh b/bin/list_local_saml_idp_endpoints.sh
new file mode 100755
index 0000000..d087b6e
--- /dev/null
+++ b/bin/list_local_saml_idp_endpoints.sh
@@ -0,0 +1,407 @@
+#!/bin/bash
+
+#######################################################################
+# Copyright 2015--2017 Internet2
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################
+
+#######################################################################
+# help message
+#######################################################################
+
+display_help () {
+/bin/cat <<- HELP_MSG
+	NOTE: Run probe_saml_idps.sh before running this script!
+	
+	Given the results of a previously executed probe, this script
+	converts the flat text files produced by that probe into a set 
+	of JSON files suitable for display on a web page. 
+	Each run of this script also produces two IdP summary files in 
+	JSON file format.
+
+	Usage: ${0##*/} [-hvq] [-23] -d IO_DIR [-o OUT_DIR] REGISTRAR_ID
+	
+	The -d option is required. The IO_DIR argument is an input 
+	directory that contains the output of the probe_shib_idps.sh 
+	script (which happens to have an identical command-line option, 
+	btw). The OUT_DIR argument to the -o option is the output 
+	directory for the current script. All of the JSON files will 
+	be written to this directory. If the -o option is omitted, the 
+	JSON files will be written to IO_DIR instead.
+	
+	By default, the script produces three disjoint lists of Shibboleth 
+	IdPs, in three separate files. One file lists all instances of 
+	Shibboleth IdP V3, a second file lists all V2 instances, and a 
+	third file lists all Shibboleth IdP instances of unknown version. 
+	If the -3 option is given, only the list of Shibboleth IdP V3 
+	instances is output. Similarly, if the -2 option is given, only
+	the list of V2 instances is output.	
+HELP_MSG
+}
+
+#######################################################################
+# Bootstrap
+#######################################################################
+
+script_name=${0##*/}  # equivalent to basename $0
+script_bin=${0%/*}  # equivalent to dirname $0
+
+#######################################################################
+# Process command-line options and arguments
+#######################################################################
+
+help_mode=false; quiet_mode=false; verbose_mode=false
+local_opts=
+while getopts ":hqvd:o:" opt; do
+	case $opt in
+		h)
+			help_mode=true
+			;;
+		q)
+			quiet_mode=true
+			verbose_mode=false
+			#local_opts="$local_opts -$opt"
+			;;
+		v)
+			quiet_mode=false
+			verbose_mode=true
+			local_opts="$local_opts -$opt"
+			;;
+		d)
+			io_dir="$OPTARG"
+			;;
+		o)
+			out_dir="$OPTARG"
+			;;
+		\?)
+			echo "ERROR: $script_name: Unrecognized option: -$OPTARG" >&2
+			exit 2
+			;;
+		:)
+			echo "ERROR: $script_name: Option -$OPTARG requires an argument" >&2
+			exit 2
+			;;
+	esac
+done
+
+if $help_mode; then
+	display_help
+	exit 0
+fi
+
+# check the IO directory
+if [ -z "$io_dir" ]; then
+	echo "ERROR: $script_name: IO_DIR (option -d) is missing" >&2
+	exit 2
+fi
+if [ ! -d "$io_dir" ]; then
+	echo "ERROR: $script_name: IO_DIR does not exist: $io_dir" >&2
+	exit 2
+fi
+$verbose_mode && printf "$script_name using IO_DIR: %s\n" "$io_dir"
+
+# check the output directory
+if [ -z "$out_dir" ]; then
+	out_dir="$io_dir"
+else
+	if [ ! -d "$out_dir" ]; then
+		echo "ERROR: $script_name: OUT_DIR does not exist: $out_dir" >&2
+		exit 2
+	fi
+fi
+$verbose_mode && printf "$script_name using OUT_DIR: %s\n" "$out_dir"
+
+shift $(( OPTIND - 1 ))
+if [ "$#" -ne 1 ]; then
+	echo "ERROR: $script_name found $# command-line arguments (1 required)" >&2
+	exit 2
+fi
+registrarID=$1
+
+#######################################################################
+# Initialization
+#######################################################################
+
+# Load the compatibility script
+COMPATIBILITY_SCRIPT="$io_dir/compatibility.sh"
+$verbose_mode && printf "$script_name sourcing compatibility script: %s\n" "$COMPATIBILITY_SCRIPT"
+source "$COMPATIBILITY_SCRIPT" >&2
+exit_status=$?
+if [ $exit_status -ne 0 ]; then
+	echo "ERROR: $script_name failed to source $COMPATIBILITY_SCRIPT (exit status $exit_status)" >&2
+	exit $exit_status
+fi
+
+# sanity check
+if [ ! -f "$IDP_ENDPOINTS_LOG_FILE" ]; then
+	printf "ERROR: IDP_ENDPOINTS_LOG_FILE does not exist: %s\n" "$IDP_ENDPOINTS_LOG_FILE" >&2
+	exit 2
+fi
+$verbose_mode && printf "$script_name using file: %s\n" "$IDP_ENDPOINTS_LOG_FILE"
+if [ ! -f "$IDP_ENDPOINTS_RESPONSIVE_FILE" ]; then
+	printf "ERROR: IDP_ENDPOINTS_RESPONSIVE_FILE does not exist: %s\n" "$IDP_ENDPOINTS_RESPONSIVE_FILE" >&2
+	exit 2
+fi
+$verbose_mode && printf "$script_name using file: %s\n" "$IDP_ENDPOINTS_RESPONSIVE_FILE"
+if [ ! -f "$IDP_ENDPOINTS_NOT_RESPONSIVE_FILE" ]; then
+	printf "ERROR: IDP_ENDPOINTS_NOT_RESPONSIVE_FILE does not exist: %s\n" "$IDP_ENDPOINTS_NOT_RESPONSIVE_FILE" >&2
+	exit 2
+fi
+$verbose_mode && printf "$script_name using file: %s\n" "$IDP_ENDPOINTS_NOT_RESPONSIVE_FILE"
+
+if $quiet_mode; then
+	# redirect stdout to the bit bucket (stderr below)
+	exec 1>/dev/null
+
+	# redirect stderr to a file
+	exec 2>"$ERROR_LOG_FILE"
+fi
+
+# determine the source lib directory
+if [ -z "$LIB_DIR" ]; then
+	echo "ERROR: $script_name requires env var LIB_DIR" >&2
+	exit 2
+fi
+if [ ! -d "$LIB_DIR" ]; then
+	echo "ERROR: $script_name: directory does not exist: $LIB_DIR" >&2
+	exit 2
+fi
+$verbose_mode && printf "$script_name using source lib directory: %s\n" "$LIB_DIR"
+
+# library filenames (always list command_paths first)
+LIB_FILENAMES="command_paths.sh
+compatible_mktemp.sh"
+
+# source lib files
+for lib_filename in $LIB_FILENAMES; do
+	lib_file="$LIB_DIR/$lib_filename"
+	if [ ! -f "$lib_file" ]; then
+		echo "ERROR: $script_name: lib file does not exist: $lib_file" >&2
+		exit 2
+	fi
+	$verbose_mode && printf "$script_name sourcing lib file: %s\n" "$lib_file"
+	source "$lib_file"
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		echo "ERROR: $script_name failed to source lib file ($status_code) $lib_file" >&2
+		exit 2
+	fi
+done
+
+# create a temporary directory
+#tmp_dir=$( make_temp_file -d )
+#if [ ! -d "$tmp_dir" ] ; then
+#	printf "ERROR: $script_name unable to create temporary dir\n" >&2
+#	exit 2
+#fi
+#$verbose_mode && printf "$script_name creating temp dir: %s\n" "$tmp_dir"
+
+#######################################################################
+# Functions
+#######################################################################
+
+escape_special_json_chars () {
+	local str="$1"
+	echo "$str" | $_SED -e 's/"/\\"/g'
+}
+
+append_json_total_object () {
+	/bin/cat <<- JSON_TOTAL_OBJECT
+	  {
+	    "heading": "$heading",
+	    "value": $( printf "%d" $value )
+	  }
+JSON_TOTAL_OBJECT
+}
+
+print_summary_endpoints () {
+
+	# begin array
+	printf "[\n"
+
+	# always emit exactly two JSON objects
+	
+	heading="# of responsive endpoints"
+	value=$( /bin/cat $IDP_ENDPOINTS_RESPONSIVE_FILE \
+		| $_GREP "$registrarID\$" \
+		| /usr/bin/wc -l
+	)
+	append_json_total_object
+
+	heading="# of non-responsive endpoints"
+	value=$( /bin/cat $IDP_ENDPOINTS_NOT_RESPONSIVE_FILE \
+		| $_GREP "$registrarID\$" \
+		| /usr/bin/wc -l
+	)
+	printf "  ,\n"
+	append_json_total_object
+
+	# end array
+	printf "]\n"
+}
+
+print_summary_non_responsive_endpoints () {
+
+	# tally the number of IdPs with one or more non-responsive endpoints
+	idp_counts=$( /bin/cat $IDP_ENDPOINTS_NOT_RESPONSIVE_FILE \
+		| $_GREP "$registrarID\$" \
+		| $_CUT -f5 \
+		| $_SORT | $_UNIQ -c
+	)
+	num_idps_with_1_non_responsive_endpoint=$( echo "$idp_counts" \
+		| $_GREP "^ *1 " \
+		| /usr/bin/wc -l
+	)
+	num_idps_with_2_non_responsive_endpoints=$( echo "$idp_counts" \
+		| $_GREP "^ *2 " \
+		| /usr/bin/wc -l
+	)
+	num_idps_with_3_non_responsive_endpoints=$( echo "$idp_counts" \
+		| $_GREP "^ *3 " \
+		| /usr/bin/wc -l
+	)
+	
+	# the rest of the IdPs have 0 non-responsive endpoints
+	num_idps_with_a_saml2_sso_endpoint=$( /bin/cat $IDP_ENDPOINTS_LOG_FILE \
+		| $_GREP "$registrarID\$" \
+		| $_CUT -f5 -d" " | $_SORT | $_UNIQ \
+		| /usr/bin/wc -l
+	)
+	# obtain the desired result by subtraction
+	num_idps_with_0_non_responsive_endpoints=$(( \
+		num_idps_with_a_saml2_sso_endpoint - \
+		num_idps_with_1_non_responsive_endpoint - \
+		num_idps_with_2_non_responsive_endpoints - \
+		num_idps_with_3_non_responsive_endpoints
+	))
+
+	# begin array
+	printf "[\n"
+
+	# always emit exactly four JSON objects (even if zero)
+	
+	heading="# of IdPs with 0 non-responsive endpoints"
+	value=$num_idps_with_0_non_responsive_endpoints
+	append_json_total_object
+
+	heading="# of IdPs with 1 non-responsive endpoints"
+	value=$num_idps_with_1_non_responsive_endpoint
+	printf "  ,\n"
+	append_json_total_object
+
+	heading="# of IdPs with 2 non-responsive endpoints"
+	value=$num_idps_with_2_non_responsive_endpoints
+	printf "  ,\n"
+	append_json_total_object
+
+	heading="# of IdPs with 3 non-responsive endpoints"
+	value=$num_idps_with_3_non_responsive_endpoints
+	printf "  ,\n"
+	append_json_total_object
+
+	# end array
+	printf "]\n"
+}
+
+append_json_object () {
+	local orgName=$( escape_special_json_chars "$orgName" )
+	local displayName=$( escape_special_json_chars "$displayName" )
+	local entityID=$( escape_special_json_chars "$entityID" )
+	local binding=$( escape_special_json_chars "$binding" )
+	local location=$( escape_special_json_chars "$location" )
+	local curlExitCode=$( escape_special_json_chars "$curlExitCode" )
+	local httpResponseCode=$( escape_special_json_chars "$httpResponseCode" )
+
+	/bin/cat <<- JSON_OBJECT
+	  {
+	    "OrganizationName": "$orgName",
+	    "DisplayName": "$displayName",
+	    "entityID": "$entityID",
+	    "Binding": "$binding",
+	    "Location": "$location",
+	    "curlExitCode": "$curlExitCode",
+	    "HTTPResponseCode": "$httpResponseCode"
+	  }
+JSON_OBJECT
+}
+
+print_list_endpoints () {
+
+	# begin output list
+	printf "[\n"
+
+	isFirstObject=true
+	# use IFS= to preserve leading and trailing whitespace
+	# (which is not actually required in this case)
+	/bin/cat - | while IFS= read -r detail_line; do
+	
+		# print leading comma unless this is the first object in the array
+		if $isFirstObject; then
+			isFirstObject=false
+		else
+			printf "  ,\n"
+		fi
+		
+		# process the detail line
+		curlExitCode=$( echo "$detail_line" | $_CUT -f1 )
+		httpResponseCode=$( echo "$detail_line" | $_CUT -f2 )
+		location=$( echo "$detail_line" | $_CUT -f3 )
+		binding=$( echo "$detail_line" | $_CUT -f4 )
+		entityID=$( echo "$detail_line" | $_CUT -f5 )
+		displayName=$( echo "$detail_line" | $_CUT -f6 )
+		orgName=$( echo "$detail_line" | $_CUT -f7 )
+		
+		append_json_object
+	done
+
+	# end output list
+	printf "]\n"
+}
+
+#######################################################################
+# Main processing
+#######################################################################
+
+# output files
+idp_endpoints_summary_file="$out_dir/local-idp-endpoints-summary.json"
+idp_endpoints_non_responsive_summary_file="$out_dir/local-idp-endpoints-non-responsive-summary.json"
+idp_endpoints_responsive_list_file="$out_dir/local-idp-endpoints-responsive-list.json"
+idp_endpoints_non_responsive_list_file="$out_dir/local-idp-endpoints-non-responsive-list.json"
+
+# clean up
+/bin/rm -f \
+	"$idp_endpoints_summary_file" \
+	"$idp_endpoints_non_responsive_summary_file" \
+	"$idp_endpoints_responsive_list_file" \
+	"$idp_endpoints_non_responsive_list_file"
+
+# print summaries
+print_summary_endpoints > "$idp_endpoints_summary_file"
+print_summary_non_responsive_endpoints > "$idp_endpoints_non_responsive_summary_file"
+
+# print list of SAML IdPs with responsive endpoints
+$verbose_mode && printf "$script_name printing file: %s\n" "$idp_endpoints_responsive_list_file"
+/bin/cat "$IDP_ENDPOINTS_RESPONSIVE_FILE" \
+	| $_GREP "$registrarID\$" \
+	| print_list_endpoints \
+	> "$idp_endpoints_responsive_list_file"
+
+# print list of SAML IdPs with non-responsive endpoints
+$verbose_mode && printf "$script_name printing file: %s\n" "$idp_endpoints_non_responsive_list_file"
+/bin/cat "$IDP_ENDPOINTS_NOT_RESPONSIVE_FILE" \
+	| $_GREP "$registrarID\$" \
+	| print_list_endpoints \
+	> "$idp_endpoints_non_responsive_list_file"
+
+exit
diff --git a/install.sh b/install.sh
index caa68d1..f2e7eee 100755
--- a/install.sh
+++ b/install.sh
@@ -94,6 +94,7 @@ while read script_file; do
 done <<SCRIPTS
 $script_bin/bin/check_idp_error_urls.sh
 $script_bin/bin/list_local_idp_error_urls.sh
+$script_bin/bin/list_local_saml_idp_endpoints.sh
 $script_bin/bin/probe_saml_idp.sh
 $script_bin/bin/probe_saml_idps.sh
 SCRIPTS