diff --git a/Makefile b/Makefile
index 428dd70b9204d6d22bdb8b6e977082ae95167586..8fd213322a1fbb663759bac3809479e2e6978318 100644
--- a/Makefile
+++ b/Makefile
@@ -4471,6 +4471,7 @@ LIBGRPC_CLI_LIBS_SRC = \
     test/cpp/util/grpc_tool.cc \
     test/cpp/util/proto_file_parser.cc \
     test/cpp/util/proto_reflection_descriptor_database.cc \
+    test/cpp/util/service_describer.cc \
 
 PUBLIC_HEADERS_CXX += \
 
@@ -15768,6 +15769,7 @@ test/cpp/util/create_test_channel.cc: $(OPENSSL_DEP)
 test/cpp/util/grpc_tool.cc: $(OPENSSL_DEP)
 test/cpp/util/proto_file_parser.cc: $(OPENSSL_DEP)
 test/cpp/util/proto_reflection_descriptor_database.cc: $(OPENSSL_DEP)
+test/cpp/util/service_describer.cc: $(OPENSSL_DEP)
 test/cpp/util/string_ref_helper.cc: $(OPENSSL_DEP)
 test/cpp/util/subprocess.cc: $(OPENSSL_DEP)
 test/cpp/util/test_config_cc.cc: $(OPENSSL_DEP)
diff --git a/build.yaml b/build.yaml
index b262e448f07a50bec6a3521a1456e64028991c9c..ac4005261c40dfaeebab9217740ca29872b4db4e 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1079,12 +1079,14 @@ libs:
   - test/cpp/util/grpc_tool.h
   - test/cpp/util/proto_file_parser.h
   - test/cpp/util/proto_reflection_descriptor_database.h
+  - test/cpp/util/service_describer.h
   src:
   - test/cpp/util/cli_call.cc
   - test/cpp/util/cli_credentials.cc
   - test/cpp/util/grpc_tool.cc
   - test/cpp/util/proto_file_parser.cc
   - test/cpp/util/proto_reflection_descriptor_database.cc
+  - test/cpp/util/service_describer.cc
   deps:
   - grpc++_reflection
   - grpc++
diff --git a/test/cpp/util/grpc_tool.cc b/test/cpp/util/grpc_tool.cc
index 3458a423d89b728f020ffd99ea44d15e928f939f..8fb325cf76eb90633c22bf9e5bd5c5710292a803 100644
--- a/test/cpp/util/grpc_tool.cc
+++ b/test/cpp/util/grpc_tool.cc
@@ -51,8 +51,13 @@
 #include "test/cpp/util/cli_call.h"
 #include "test/cpp/util/proto_file_parser.h"
 #include "test/cpp/util/proto_reflection_descriptor_database.h"
+#include "test/cpp/util/service_describer.h"
 #include "test/cpp/util/test_config.h"
 
+namespace grpc {
+namespace testing {
+
+DEFINE_bool(l, false, "Use a long listing format");
 DEFINE_bool(remotedb, true, "Use server types to parse and format messages");
 DEFINE_string(metadata, "",
               "Metadata to send to server, in the form of key1:val1:key2:val2");
@@ -62,8 +67,6 @@ DEFINE_bool(binary_input, false, "Input in binary format");
 DEFINE_bool(binary_output, false, "Output in binary format");
 DEFINE_string(infile, "", "Input file (default is stdin)");
 
-namespace grpc {
-namespace testing {
 namespace {
 
 class GrpcTool {
@@ -75,6 +78,8 @@ class GrpcTool {
             GrpcToolOutputCallback callback);
   bool CallMethod(int argc, const char** argv, const CliCredentials& cred,
                   GrpcToolOutputCallback callback);
+  bool ListServices(int argc, const char** argv, const CliCredentials& cred,
+                    GrpcToolOutputCallback callback);
   bool PrintType(int argc, const char** argv, const CliCredentials& cred,
                  GrpcToolOutputCallback callback);
   // TODO(zyc): implement the following methods
@@ -165,8 +170,8 @@ struct Command {
 
 const Command ops[] = {
     {"help", BindWith5Args(&GrpcTool::Help), 0, INT_MAX},
-    // {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},
-    // {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},
+    {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},
+    {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},
     {"call", BindWith5Args(&GrpcTool::CallMethod), 2, 3},
     {"type", BindWith5Args(&GrpcTool::PrintType), 2, 2},
     // {"parse", BindWith5Args(&GrpcTool::ParseMessage), 2, 3},
@@ -178,7 +183,7 @@ void Usage(const grpc::string& msg) {
   fprintf(
       stderr,
       "%s\n"
-      // "  grpc_cli ls ...         ; List services\n"
+      "  grpc_cli ls ...         ; List services\n"
       "  grpc_cli call ...       ; Call method\n"
       "  grpc_cli type ...       ; Print type\n"
       // "  grpc_cli parse ...      ; Parse message\n"
@@ -257,6 +262,106 @@ bool GrpcTool::Help(int argc, const char** argv, const CliCredentials& cred,
   return true;
 }
 
+bool GrpcTool::ListServices(int argc, const char** argv,
+                            const CliCredentials& cred,
+                            GrpcToolOutputCallback callback) {
+  CommandUsage(
+      "List services\n"
+      "  grpc_cli ls <address> [<service>[/<method>]]\n"
+      "    <address>                ; host:port\n"
+      "    <service>                ; Exported service name\n"
+      "    <method>                 ; Method name\n"
+      "    --l                      ; Use a long listing format\n"
+      "    --outfile                ; Output filename (defaults to stdout)\n" +
+      cred.GetCredentialUsage());
+
+  grpc::string server_address(argv[0]);
+  std::shared_ptr<grpc::Channel> channel =
+      grpc::CreateChannel(server_address, cred.GetCredentials());
+  grpc::ProtoReflectionDescriptorDatabase desc_db(channel);
+  grpc::protobuf::DescriptorPool desc_pool(&desc_db);
+
+  std::vector<grpc::string> service_list;
+  if (!desc_db.GetServices(&service_list)) {
+    return false;
+  }
+
+  // If no service is specified, dump the list of services.
+  grpc::string output;
+  if (argc < 2) {
+    // List all services, if --l is passed, then include full description,
+    // otherwise include a summarized list only.
+    if (FLAGS_l) {
+      output = DescribeServiceList(service_list, desc_pool);
+    } else {
+      for (auto it = service_list.begin(); it != service_list.end(); it++) {
+        auto const& service = *it;
+        output.append(service);
+        output.append("\n");
+      }
+    }
+  } else {
+    grpc::string service_name;
+    grpc::string method_name;
+    std::stringstream ss(argv[1]);
+
+    // Remove leading slashes.
+    while (ss.peek() == '/') {
+      ss.get();
+    }
+
+    // Parse service and method names. Support the following patterns:
+    //   Service
+    //   Service Method
+    //   Service.Method
+    //   Service/Method
+    if (argc == 3) {
+      std::getline(ss, service_name, '/');
+      method_name = argv[2];
+    } else {
+      if (std::getline(ss, service_name, '/')) {
+        std::getline(ss, method_name);
+      }
+    }
+
+    const grpc::protobuf::ServiceDescriptor* service =
+        desc_pool.FindServiceByName(service_name);
+    if (service != nullptr) {
+      if (method_name.empty()) {
+        output = FLAGS_l ? DescribeService(service) : SummarizeService(service);
+      } else {
+        method_name.insert(0, 1, '.');
+        method_name.insert(0, service_name);
+        const grpc::protobuf::MethodDescriptor* method =
+            desc_pool.FindMethodByName(method_name);
+        if (method != nullptr) {
+          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
+        } else {
+          fprintf(stderr, "Method %s not found in service %s.\n",
+                  method_name.c_str(), service_name.c_str());
+          return false;
+        }
+      }
+    } else {
+      if (!method_name.empty()) {
+        fprintf(stderr, "Service %s not found.\n", service_name.c_str());
+        return false;
+      } else {
+        const grpc::protobuf::MethodDescriptor* method =
+            desc_pool.FindMethodByName(service_name);
+        if (method != nullptr) {
+          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
+        } else {
+          fprintf(stderr, "Service or method %s not found.\n",
+                  service_name.c_str());
+          return false;
+        }
+      }
+    }
+  }
+  return callback(output);
+}
+
 bool GrpcTool::PrintType(int argc, const char** argv,
                          const CliCredentials& cred,
                          GrpcToolOutputCallback callback) {
diff --git a/test/cpp/util/grpc_tool_test.cc b/test/cpp/util/grpc_tool_test.cc
index edb66b72d83736bc2eb9a3e01c22a6b5eb726bec..bad1579f11eab9707f9ef8876fbf19340b806586 100644
--- a/test/cpp/util/grpc_tool_test.cc
+++ b/test/cpp/util/grpc_tool_test.cc
@@ -35,6 +35,7 @@
 
 #include <sstream>
 
+#include <gflags/gflags.h>
 #include <grpc++/channel.h>
 #include <grpc++/client_context.h>
 #include <grpc++/create_channel.h>
@@ -55,8 +56,41 @@
 using grpc::testing::EchoRequest;
 using grpc::testing::EchoResponse;
 
+#define USAGE_REGEX "(  grpc_cli .+\n){2,10}"
+
+#define ECHO_TEST_SERVICE_SUMMARY \
+  "Echo\n"                        \
+  "RequestStream\n"               \
+  "ResponseStream\n"              \
+  "BidiStream\n"                  \
+  "Unimplemented\n"
+
+#define ECHO_TEST_SERVICE_DESCRIPTION                                         \
+  "filename: src/proto/grpc/testing/echo.proto\n"                             \
+  "package: grpc.testing;\n"                                                  \
+  "service EchoTestService {\n"                                               \
+  "  rpc Echo(grpc.testing.EchoRequest) returns (grpc.testing.EchoResponse) " \
+  "{}\n"                                                                      \
+  "  rpc RequestStream(stream grpc.testing.EchoRequest) returns "             \
+  "(grpc.testing.EchoResponse) {}\n"                                          \
+  "  rpc ResponseStream(grpc.testing.EchoRequest) returns (stream "           \
+  "grpc.testing.EchoResponse) {}\n"                                           \
+  "  rpc BidiStream(stream grpc.testing.EchoRequest) returns (stream "        \
+  "grpc.testing.EchoResponse) {}\n"                                           \
+  "  rpc Unimplemented(grpc.testing.EchoRequest) returns "                    \
+  "(grpc.testing.EchoResponse) {}\n"                                          \
+  "}\n"                                                                       \
+  "\n"
+
+#define ECHO_METHOD_DESCRIPTION                                               \
+  "  rpc Echo(grpc.testing.EchoRequest) returns (grpc.testing.EchoResponse) " \
+  "{}\n"
+
 namespace grpc {
 namespace testing {
+
+DECLARE_bool(l);
+
 namespace {
 
 class TestCliCredentials GRPC_FINAL : public grpc::testing::CliCredentials {
@@ -68,6 +102,17 @@ class TestCliCredentials GRPC_FINAL : public grpc::testing::CliCredentials {
   const grpc::string GetCredentialUsage() const GRPC_OVERRIDE { return ""; }
 };
 
+bool PrintStream(std::stringstream* ss, const grpc::string& output) {
+  (*ss) << output;
+  return true;
+}
+
+template <typename T>
+size_t ArraySize(T& a) {
+  return ((sizeof(a) / sizeof(*(a))) /
+          static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));
+}
+
 }  // namespame
 
 class TestServiceImpl : public ::grpc::testing::EchoTestService::Service {
@@ -122,19 +167,6 @@ class GrpcToolTest : public ::testing::Test {
   reflection::ProtoServerReflectionPlugin plugin_;
 };
 
-static bool PrintStream(std::stringstream* ss, const grpc::string& output) {
-  (*ss) << output;
-  return true;
-}
-
-template <typename T>
-static size_t ArraySize(T& a) {
-  return ((sizeof(a) / sizeof(*(a))) /
-          static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));
-}
-
-#define USAGE_REGEX "(  grpc_cli .+\n){2,10}"
-
 TEST_F(GrpcToolTest, NoCommand) {
   // Test input "grpc_cli"
   std::stringstream output_stream;
@@ -176,6 +208,54 @@ TEST_F(GrpcToolTest, HelpCommand) {
   EXPECT_TRUE(0 == output_stream.tellp());
 }
 
+TEST_F(GrpcToolTest, ListCommand) {
+  // Test input "grpc_cli list localhost:<port>"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str()};
+
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(),
+                          "grpc.testing.EchoTestService\n"
+                          "grpc.reflection.v1alpha.ServerReflection\n"));
+
+  ShutdownServer();
+}
+
+TEST_F(GrpcToolTest, ListOneService) {
+  // Test input "grpc_cli list localhost:<port> grpc.testing.EchoTestService"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str(),
+                        "grpc.testing.EchoTestService"};
+  // without -l flag
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_TEST_SERVICE_SUMMARY
+  EXPECT_TRUE(0 ==
+              strcmp(output_stream.str().c_str(), ECHO_TEST_SERVICE_SUMMARY));
+
+  // with -l flag
+  output_stream.str(grpc::string());
+  output_stream.clear();
+  FLAGS_l = true;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_TEST_SERVICE_DESCRIPTION
+  EXPECT_TRUE(
+      0 == strcmp(output_stream.str().c_str(), ECHO_TEST_SERVICE_DESCRIPTION));
+
+  ShutdownServer();
+}
+
 TEST_F(GrpcToolTest, TypeCommand) {
   // Test input "grpc_cli type localhost:<port> grpc.testing.EchoRequest"
   std::stringstream output_stream;
@@ -197,6 +277,35 @@ TEST_F(GrpcToolTest, TypeCommand) {
   ShutdownServer();
 }
 
+TEST_F(GrpcToolTest, ListOneMethod) {
+  // Test input "grpc_cli list localhost:<port> grpc.testing.EchoTestService"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str(),
+                        "grpc.testing.EchoTestService.Echo"};
+  // without -l flag
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: "Echo"
+  EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(), "Echo\n"));
+
+  // with -l flag
+  output_stream.str(grpc::string());
+  output_stream.clear();
+  FLAGS_l = true;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_METHOD_DESCRIPTION
+  EXPECT_TRUE(0 ==
+              strcmp(output_stream.str().c_str(), ECHO_METHOD_DESCRIPTION));
+
+  ShutdownServer();
+}
+
 TEST_F(GrpcToolTest, TypeNotFound) {
   // Test input "grpc_cli type localhost:<port> grpc.testing.DummyRequest"
   std::stringstream output_stream;
@@ -214,7 +323,7 @@ TEST_F(GrpcToolTest, TypeNotFound) {
 }
 
 TEST_F(GrpcToolTest, CallCommand) {
-  // Test input "grpc_cli call Echo"
+  // Test input "grpc_cli call localhost:<port> Echo "message: 'Hello'"
   std::stringstream output_stream;
 
   const grpc::string server_address = SetUpServer();
@@ -231,7 +340,7 @@ TEST_F(GrpcToolTest, CallCommand) {
 }
 
 TEST_F(GrpcToolTest, TooFewArguments) {
-  // Test input "grpc_cli call localhost:<port> Echo "message: 'Hello'"
+  // Test input "grpc_cli call Echo"
   std::stringstream output_stream;
   const char* argv[] = {"grpc_cli", "call", "Echo"};
 
diff --git a/test/cpp/util/service_describer.cc b/test/cpp/util/service_describer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4fe4a748052457ae9ed602b837e293f882ccf4fc
--- /dev/null
+++ b/test/cpp/util/service_describer.cc
@@ -0,0 +1,108 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "test/cpp/util/service_describer.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace grpc {
+namespace testing {
+
+grpc::string DescribeServiceList(std::vector<grpc::string> service_list,
+                                 grpc::protobuf::DescriptorPool& desc_pool) {
+  std::stringstream result;
+  for (auto it = service_list.begin(); it != service_list.end(); it++) {
+    auto const& service = *it;
+    const grpc::protobuf::ServiceDescriptor* service_desc =
+        desc_pool.FindServiceByName(service);
+    if (service_desc != nullptr) {
+      result << DescribeService(service_desc);
+    }
+  }
+  return result.str();
+}
+
+grpc::string DescribeService(const grpc::protobuf::ServiceDescriptor* service) {
+  grpc::string result;
+  if (service->options().deprecated()) {
+    result.append("DEPRECATED\n");
+  }
+  result.append("filename: " + service->file()->name() + "\n");
+
+  grpc::string package = service->full_name();
+  size_t pos = package.rfind("." + service->name());
+  if (pos != grpc::string::npos) {
+    package.erase(pos);
+    result.append("package: " + package + ";\n");
+  }
+  result.append("service " + service->name() + " {\n");
+  for (int i = 0; i < service->method_count(); ++i) {
+    result.append(DescribeMethod(service->method(i)));
+  }
+  result.append("}\n\n");
+  return result;
+}
+
+grpc::string DescribeMethod(const grpc::protobuf::MethodDescriptor* method) {
+  std::stringstream result;
+  result << "  rpc " << method->name()
+         << (method->client_streaming() ? "(stream " : "(")
+         << method->input_type()->full_name() << ") returns "
+         << (method->server_streaming() ? "(stream " : "(")
+         << method->output_type()->full_name() << ") {}\n";
+  if (method->options().deprecated()) {
+    result << " DEPRECATED";
+  }
+  return result.str();
+}
+
+grpc::string SummarizeService(
+    const grpc::protobuf::ServiceDescriptor* service) {
+  grpc::string result;
+  for (int i = 0; i < service->method_count(); ++i) {
+    result.append(SummarizeMethod(service->method(i)));
+  }
+  return result;
+}
+
+grpc::string SummarizeMethod(const grpc::protobuf::MethodDescriptor* method) {
+  grpc::string result = method->name();
+  result.append("\n");
+  return result;
+}
+
+}  // namespace testing
+}  // namespace grpc
diff --git a/test/cpp/util/service_describer.h b/test/cpp/util/service_describer.h
new file mode 100644
index 0000000000000000000000000000000000000000..02c58e84c432971c50cf7ea1bf1c139dbefa12f7
--- /dev/null
+++ b/test/cpp/util/service_describer.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_TEST_CPP_UTIL_SERVICE_DESCRIBER_H
+#define GRPC_TEST_CPP_UTIL_SERVICE_DESCRIBER_H
+
+#include <grpc++/support/config.h>
+#include "test/cpp/util/config_grpc_cli.h"
+
+namespace grpc {
+namespace testing {
+
+grpc::string DescribeServiceList(std::vector<grpc::string> service_list,
+                                 grpc::protobuf::DescriptorPool& desc_pool);
+
+grpc::string DescribeService(const grpc::protobuf::ServiceDescriptor* service);
+
+grpc::string DescribeMethod(const grpc::protobuf::MethodDescriptor* method);
+
+grpc::string SummarizeService(const grpc::protobuf::ServiceDescriptor* service);
+
+grpc::string SummarizeMethod(const grpc::protobuf::MethodDescriptor* method);
+
+}  // namespase testing
+}  // namespace grpc
+
+#endif  // GRPC_TEST_CPP_UTIL_SERVICE_DESCRIBER_H
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index b5d26862d6d8a3d206082ccd99af38e899a6737b..ded6be7527e026af04d1707b4870c8b7f3a1408f 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -4668,7 +4668,8 @@
       "test/cpp/util/config_grpc_cli.h", 
       "test/cpp/util/grpc_tool.h", 
       "test/cpp/util/proto_file_parser.h", 
-      "test/cpp/util/proto_reflection_descriptor_database.h"
+      "test/cpp/util/proto_reflection_descriptor_database.h", 
+      "test/cpp/util/service_describer.h"
     ], 
     "language": "c++", 
     "name": "grpc_cli_libs", 
@@ -4683,7 +4684,9 @@
       "test/cpp/util/proto_file_parser.cc", 
       "test/cpp/util/proto_file_parser.h", 
       "test/cpp/util/proto_reflection_descriptor_database.cc", 
-      "test/cpp/util/proto_reflection_descriptor_database.h"
+      "test/cpp/util/proto_reflection_descriptor_database.h", 
+      "test/cpp/util/service_describer.cc", 
+      "test/cpp/util/service_describer.h"
     ], 
     "third_party": false, 
     "type": "lib"
diff --git a/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj b/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj
index 425b66f155f1f4b81b12cbc12fabe5dca49959a7..2790884ee1bc086a7dc096178b0b6b9145a5683d 100644
--- a/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj
+++ b/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj
@@ -153,6 +153,7 @@
     <ClInclude Include="$(SolutionDir)\..\test\cpp\util\grpc_tool.h" />
     <ClInclude Include="$(SolutionDir)\..\test\cpp\util\proto_file_parser.h" />
     <ClInclude Include="$(SolutionDir)\..\test\cpp\util\proto_reflection_descriptor_database.h" />
+    <ClInclude Include="$(SolutionDir)\..\test\cpp\util\service_describer.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="$(SolutionDir)\..\test\cpp\util\cli_call.cc">
@@ -165,6 +166,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\test\cpp\util\proto_reflection_descriptor_database.cc">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\test\cpp\util\service_describer.cc">
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc++_reflection\grpc++_reflection.vcxproj">
diff --git a/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj.filters b/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj.filters
index b2128c282bfc6fbbc190adbebc9d79fa152f3d27..d4d692766d6064f3a8c2e482a8b37b555700cfda 100644
--- a/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_cli_libs/grpc_cli_libs.vcxproj.filters
@@ -16,6 +16,9 @@
     <ClCompile Include="$(SolutionDir)\..\test\cpp\util\proto_reflection_descriptor_database.cc">
       <Filter>test\cpp\util</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\test\cpp\util\service_describer.cc">
+      <Filter>test\cpp\util</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="$(SolutionDir)\..\test\cpp\util\cli_call.h">
@@ -36,6 +39,9 @@
     <ClInclude Include="$(SolutionDir)\..\test\cpp\util\proto_reflection_descriptor_database.h">
       <Filter>test\cpp\util</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\test\cpp\util\service_describer.h">
+      <Filter>test\cpp\util</Filter>
+    </ClInclude>
   </ItemGroup>
 
   <ItemGroup>