diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp
index 0fc0a23fabeb678375f9c357937231c5ae80795d..436edbc2d21d794c2b26e4065c920a271825c914 100644
--- a/arbor/include/arbor/mechcat.hpp
+++ b/arbor/include/arbor/mechcat.hpp
@@ -110,6 +110,9 @@ public:
 
    ~mechanism_catalogue();
 
+   // Grab a collection of all mechanism names in the catalogue.
+   std::vector<std::string> mechanism_names() const;
+
 private:
     std::unique_ptr<catalogue_state> state_;
 
diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp
index 3fac9e805dc2aeabe7d479d6682c5ce57ab56ccc..001ab95eb75dca4b06f9465239328997ac01a1b9 100644
--- a/arbor/mechcat.cpp
+++ b/arbor/mechcat.cpp
@@ -8,6 +8,7 @@
 #include <arbor/mechcat.hpp>
 #include <arbor/util/expected.hpp>
 
+#include "util/rangeutil.hpp"
 #include "util/maputil.hpp"
 
 /* Notes on implementation:
@@ -479,6 +480,14 @@ struct catalogue_state {
         return over;
     }
 
+    // Collect all mechanism names present in this catalogue
+    std::vector<std::string> mechanism_names() const {
+        std::vector<std::string> result;
+        util::assign(result, util::keys(info_map_));
+        util::append(result, util::keys(derived_map_));
+        return result;
+    }
+
     // Schemata for (un-derived) mechanisms.
     string_map<mechanism_info_ptr> info_map_;
 
@@ -495,6 +504,10 @@ mechanism_catalogue::mechanism_catalogue():
     state_(new catalogue_state)
 {}
 
+std::vector<std::string> mechanism_catalogue::mechanism_names() const {
+    return state_->mechanism_names();
+}
+
 mechanism_catalogue::mechanism_catalogue(mechanism_catalogue&& other) = default;
 mechanism_catalogue& mechanism_catalogue::operator=(mechanism_catalogue&& other) = default;
 
diff --git a/doc/python/mechanisms.rst b/doc/python/mechanisms.rst
index e31f137a2979f171ee10f3a83938406282e5d78b..b55aa460a8604f180eb5e60bded2a30eeda5c184 100644
--- a/doc/python/mechanisms.rst
+++ b/doc/python/mechanisms.rst
@@ -280,6 +280,12 @@ Mechanism catalogues
         :return: mechanism metadata
         :rtype: :class:`mechanism_info`
 
+    .. py:method:: names()
+
+        Return a list names of all the mechanisms in the catalogue.
+
+        :return: list
+
     .. py:method:: derive(name, parent, globals={}, ions={})
 
         Derive a new mechanism with *name* from the mechanism *parent*.
diff --git a/python/mechanism.cpp b/python/mechanism.cpp
index 9c65cc2d8858cf3b3e2bfd991109296e87e67c75..e0fc984636d7c53e140cd4ed6cb8f8e77a1bd683 100644
--- a/python/mechanism.cpp
+++ b/python/mechanism.cpp
@@ -107,7 +107,9 @@ void register_mechanisms(pybind11::module& m) {
     cat
         .def(pybind11::init<const arb::mechanism_catalogue&>())
         .def("has", &arb::mechanism_catalogue::has,
-                "name"_a, "Is 'name' in the catalogue?")
+             "name"_a, "Is 'name' in the catalogue?")
+        .def("keys", &arb::mechanism_catalogue::mechanism_names,
+             "Return a list of all mechanisms in this catalogues.")
         .def("is_derived", &arb::mechanism_catalogue::is_derived,
                 "name"_a, "Is 'name' a derived mechanism or can it be implicitly derived?")
         .def("__getitem__",
diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp
index 31526aa2c8e840861ace53879666658d94129ee5..ffd6b26510c9d79564609062db3bb7ae7c66cd60 100644
--- a/test/unit/test_mechcat.cpp
+++ b/test/unit/test_mechcat.cpp
@@ -244,6 +244,48 @@ TEST(mechcat, fingerprint) {
         arb::fingerprint_mismatch);
 }
 
+TEST(mechcat, names) {
+    // All names are caught; covers `add' and `derive'
+    {
+        auto cat = build_fake_catalogue();
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble", "fleeb", "fleeb1", "fleeb2", "fleeb3", "special_fleeb"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Deriving names does not add to catalogue
+    {
+        auto cat = build_fake_catalogue();
+        auto info   = cat["burble/quux=3,xyzzy=4"];
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble", "fleeb", "fleeb1", "fleeb2", "fleeb3", "special_fleeb"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Deleting a mechanism removes it and all derived from it.
+    {
+        auto cat = build_fake_catalogue();
+        cat.remove("fleeb");
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Empty means empty.
+    {
+        auto cat = build_fake_catalogue();
+        cat.remove("fleeb");
+        cat.remove("burble");
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+}
+
 TEST(mechcat, derived_info) {
     auto cat = build_fake_catalogue();