diff --git a/src/mds/MDS.cc b/src/mds/MDS.cc
index 1679328a1a8..148683159a4 100644
--- a/src/mds/MDS.cc
+++ b/src/mds/MDS.cc
@@ -367,6 +367,15 @@ bool MDS::asok_command(string command, cmdmap_t& cmdmap, string format,
       mds_lock.Lock();
       mdcache->force_readonly();
       mds_lock.Unlock();
+    } else if (command == "dirfrag split") {
+      Mutex::Locker l(mds_lock);
+      command_dirfrag_split(cmdmap, ss);
+    } else if (command == "dirfrag merge") {
+      Mutex::Locker l(mds_lock);
+      command_dirfrag_merge(cmdmap, ss);
+    } else if (command == "dirfrag ls") {
+      Mutex::Locker l(mds_lock);
+      command_dirfrag_ls(cmdmap, ss, f);
     }
   }
   f->flush(ss);
@@ -599,6 +608,150 @@ int MDS::_command_export_dir(
   return 0;
 }
 
+CDir *MDS::_command_dirfrag_get(
+    const cmdmap_t &cmdmap,
+    std::ostream &ss)
+{
+  std::string path;
+  bool got = cmd_getval(g_ceph_context, cmdmap, "path", path);
+  if (!got) {
+    ss << "missing path argument";
+    return NULL;
+  }
+
+  std::string frag_str;
+  if (!cmd_getval(g_ceph_context, cmdmap, "frag", frag_str)) {
+    ss << "missing frag argument";
+    return NULL;
+  }
+
+  CInode *in = mdcache->cache_traverse(filepath(path.c_str()));
+  if (!in) {
+    // TODO really we should load something in if it's not in cache,
+    // but the infrastructure is harder, and we might still be unable
+    // to act on it if someone else is auth.
+    ss << "directory inode not in cache";
+    return NULL;
+  }
+
+  frag_t fg;
+
+  if (!fg.parse(frag_str.c_str())) {
+    ss << "frag " << frag_str << " failed to parse";
+    return NULL;
+  }
+
+  CDir *dir = in->get_dirfrag(fg);
+  if (!dir) {
+    ss << "frag 0x" << std::hex << in->ino() << "/" << fg << " not found";
+    return NULL;
+  }
+
+  if (!dir->is_auth()) {
+    ss << "frag " << dir->dirfrag() << " not auth (auth = "
+       << dir->authority() << ")";
+    return NULL;
+  }
+
+  return dir;
+}
+
+bool MDS::command_dirfrag_split(
+    cmdmap_t cmdmap,
+    std::ostream &ss)
+{
+  int64_t by = 0;
+  if (!cmd_getval(g_ceph_context, cmdmap, "bits", by)) {
+    ss << "missing bits argument";
+    return false;
+  }
+
+  if (by <= 0) {
+    ss << "must split by >0 bits";
+    return false;
+  }
+
+  CDir *dir = _command_dirfrag_get(cmdmap, ss);
+  if (!dir) {
+    return false;
+  }
+
+  mdcache->split_dir(dir, by);
+
+  return true;
+}
+
+bool MDS::command_dirfrag_merge(
+    cmdmap_t cmdmap,
+    std::ostream &ss)
+{
+  std::string path;
+  bool got = cmd_getval(g_ceph_context, cmdmap, "path", path);
+  if (!got) {
+    ss << "missing path argument";
+    return false;
+  }
+
+  std::string frag_str;
+  if (!cmd_getval(g_ceph_context, cmdmap, "frag", frag_str)) {
+    ss << "missing frag argument";
+    return false;
+  }
+
+  CInode *in = mdcache->cache_traverse(filepath(path.c_str()));
+  if (!in) {
+    ss << "directory inode not in cache";
+    return false;
+  }
+
+  frag_t fg;
+  if (!fg.parse(frag_str.c_str())) {
+    ss << "frag " << frag_str << " failed to parse";
+    return false;
+  }
+
+  mdcache->merge_dir(in, fg);
+
+  return true;
+}
+
+bool MDS::command_dirfrag_ls(
+    cmdmap_t cmdmap,
+    std::ostream &ss,
+    Formatter *f)
+{
+  std::string path;
+  bool got = cmd_getval(g_ceph_context, cmdmap, "path", path);
+  if (!got) {
+    ss << "missing path argument";
+    return false;
+  }
+
+  CInode *in = mdcache->cache_traverse(filepath(path.c_str()));
+  if (!in) {
+    ss << "directory inode not in cache";
+    return false;
+  }
+
+  f->open_array_section("frags");
+  std::list<frag_t> frags;
+  // NB using get_leaves_under instead of get_dirfrags to give
+  // you the list of what dirfrags may exist, not which are in cache
+  in->dirfragtree.get_leaves_under(frag_t(), frags);
+  for (std::list<frag_t>::iterator i = frags.begin();
+       i != frags.end(); ++i) {
+    f->open_object_section("frag");
+    f->dump_int("value", i->value());
+    f->dump_int("bits", i->bits());
+    std::ostringstream frag_str;
+    frag_str << std::hex << i->value() << "/" << std::dec << i->bits();
+    f->dump_string("str", frag_str.str());
+    f->close_section();
+  }
+  f->close_section();
+
+  return true;
+}
 
 void MDS::set_up_admin_socket()
 {
@@ -672,6 +825,27 @@ void MDS::set_up_admin_socket()
 				     asok_hook,
 				     "Return the subtree map");
   assert(r == 0);
+  r = admin_socket->register_command("dirfrag split",
+				     "dirfrag split "
+                                     "name=path,type=CephString,req=true "
+                                     "name=frag,type=CephString,req=true "
+                                     "name=bits,type=CephInt,req=true ",
+				     asok_hook,
+				     "Fragment directory by path");
+  assert(r == 0);
+  r = admin_socket->register_command("dirfrag merge",
+				     "dirfrag merge "
+                                     "name=path,type=CephString,req=true "
+                                     "name=frag,type=CephString,req=true",
+				     asok_hook,
+				     "De-fragment directory by path");
+  assert(r == 0);
+  r = admin_socket->register_command("dirfrag ls",
+				     "dirfrag ls "
+                                     "name=path,type=CephString,req=true",
+				     asok_hook,
+				     "List fragments in directory");
+  assert(r == 0);
 }
 
 void MDS::clean_up_admin_socket()
@@ -1265,7 +1439,7 @@ COMMAND("heap " \
 	"mds", "*", "cli,rest")
 };
 
-// FIXME: reinstate issue_caps, try_eval, fragment_dir, merge_dir
+// FIXME: reinstate issue_caps, try_eval,
 //  *if* it makes sense to do so (or should these be admin socket things?)
 
 /* This function DOES put the passed message before returning*/
diff --git a/src/mds/MDS.h b/src/mds/MDS.h
index f7335e46bca..c120dea10fa 100644
--- a/src/mds/MDS.h
+++ b/src/mds/MDS.h
@@ -382,9 +382,22 @@ private:
   void command_get_subtrees(Formatter *f);
   void command_export_dir(Formatter *f,
       const std::string &path, mds_rank_t dest);
+  bool command_dirfrag_split(
+      cmdmap_t cmdmap,
+      std::ostream &ss);
+  bool command_dirfrag_merge(
+      cmdmap_t cmdmap,
+      std::ostream &ss);
+  bool command_dirfrag_ls(
+      cmdmap_t cmdmap,
+      std::ostream &ss,
+      Formatter *f);
  private:
   int _command_export_dir(const std::string &path, mds_rank_t dest);
   int _command_flush_journal(std::stringstream *ss);
+  CDir *_command_dirfrag_get(
+      const cmdmap_t &cmdmap,
+      std::ostream &ss);
  public:
     // config observer bits
   virtual const char** get_tracked_conf_keys() const;