diff --git a/doc/openocd.texi b/doc/openocd.texi
index c55ae482b839dea1c90077b5c67e6c91a4e50334..b952060437b1d298300c9d94e208212442c430dc 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -2354,9 +2354,16 @@ Actual config files use a variable instead of literals like
 @option{str912}, to support more than one chip of each type.
 @xref{Config File Guidelines}.
 
-At this writing there is only a single command to work with
-scan chains, and there is no support for enumerating
-TAPs or examining their attributes.
+@deffn Command {jtag names}
+Returns the names of all current TAPs in the scan chain.
+Use @command{jtag cget} or @command{jtag tapisenabled}
+to examine attributes and state of each TAP.
+@example
+foreach t [jtag names] @{
+    puts [format "TAP: %s\n" $t]
+@}
+@end example
+@end deffn
 
 @deffn Command {scan_chain}
 Displays the TAPs in the scan chain configuration,
@@ -2369,10 +2376,8 @@ In addition to the enable/disable status, the contents of
 each TAP's instruction register can also change.
 @end deffn
 
-@c FIXME!  there should be commands to enumerate TAPs
-@c and get their attributes, like there are for targets.
-@c "jtag cget ..." will handle attributes.
-@c "jtag names" for enumerating TAPs, maybe.
+@c FIXME!  "jtag cget" should be able to return all TAP
+@c attributes, like "$target_name cget" does for targets.
 
 @c Probably want "jtag eventlist", and a "tap-reset" event
 @c (on entry to RESET state).
diff --git a/src/jtag/tcl.c b/src/jtag/tcl.c
index 759f8f7c4125f33c1b228a06ee528dd6451c2d26..dda529618eb37b58753570250bced9193469ac6e 100644
--- a/src/jtag/tcl.c
+++ b/src/jtag/tcl.c
@@ -391,7 +391,8 @@ static int jim_jtag_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 		JTAG_CMD_TAPDISABLE,
 		JTAG_CMD_TAPISENABLED,
 		JTAG_CMD_CONFIGURE,
-		JTAG_CMD_CGET
+		JTAG_CMD_CGET,
+		JTAG_CMD_NAMES,
 	};
 
 	const Jim_Nvp jtag_cmds[] = {
@@ -403,6 +404,7 @@ static int jim_jtag_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 		{ .name = "tapdisable"    , .value = JTAG_CMD_TAPDISABLE },
 		{ .name = "configure"     , .value = JTAG_CMD_CONFIGURE },
 		{ .name = "cget"          , .value = JTAG_CMD_CGET },
+		{ .name = "names"         , .value = JTAG_CMD_NAMES },
 
 		{ .name = NULL, .value = -1 },
 	};
@@ -497,7 +499,8 @@ static int jim_jtag_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 
 	case JTAG_CMD_CGET:
 		if (goi.argc < 2) {
-			Jim_WrongNumArgs(goi.interp, 0, NULL, "?tap-name? -option ...");
+			Jim_WrongNumArgs(goi.interp, 0, NULL,
+					"cget tap_name queryparm");
 			return JIM_ERR;
 		}
 
@@ -517,7 +520,8 @@ static int jim_jtag_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 
 	case JTAG_CMD_CONFIGURE:
 		if (goi.argc < 3) {
-			Jim_WrongNumArgs(goi.interp, 0, NULL, "?tap-name? -option ?VALUE? ...");
+			Jim_WrongNumArgs(goi.interp, 0, NULL,
+					"configure tap_name attribute value ...");
 			return JIM_ERR;
 		}
 
@@ -533,6 +537,27 @@ static int jim_jtag_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 			goi.isconfigure = 1;
 			return jtag_tap_configure_cmd(&goi, t);
 		}
+		break;
+
+	case JTAG_CMD_NAMES:
+		if (goi.argc != 0) {
+			Jim_WrongNumArgs(goi.interp, 1, goi.argv, "Too many parameters");
+			return JIM_ERR;
+		}
+		Jim_SetResult(goi.interp, Jim_NewListObj(goi.interp, NULL, 0));
+		{
+			jtag_tap_t *tap;
+
+			for (tap = jtag_all_taps(); tap; tap = tap->next_tap) {
+				Jim_ListAppendElement(goi.interp,
+					Jim_GetResult(goi.interp),
+					Jim_NewStringObj(goi.interp,
+						tap->dotted_name, -1));
+			}
+			return JIM_OK;
+		}
+		break;
+
 	}
 
 	return JIM_ERR;