diff --git a/BUILD b/BUILD
index d300cc632ee0fe2cd480e6605d157a35cd0e0c24..79b71c6726f2eeb92469672e54c37e209d88551c 100644
--- a/BUILD
+++ b/BUILD
@@ -248,8 +248,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.h",
     "src/core/lib/transport/metadata.h",
     "src/core/lib/transport/metadata_batch.h",
-    "src/core/lib/transport/method_config.h",
     "src/core/lib/transport/pid_controller.h",
+    "src/core/lib/transport/service_config.h",
     "src/core/lib/transport/static_metadata.h",
     "src/core/lib/transport/timeout_encoding.h",
     "src/core/lib/transport/transport.h",
@@ -438,8 +438,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.c",
     "src/core/lib/transport/metadata.c",
     "src/core/lib/transport/metadata_batch.c",
-    "src/core/lib/transport/method_config.c",
     "src/core/lib/transport/pid_controller.c",
+    "src/core/lib/transport/service_config.c",
     "src/core/lib/transport/static_metadata.c",
     "src/core/lib/transport/timeout_encoding.c",
     "src/core/lib/transport/transport.c",
@@ -684,8 +684,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.h",
     "src/core/lib/transport/metadata.h",
     "src/core/lib/transport/metadata_batch.h",
-    "src/core/lib/transport/method_config.h",
     "src/core/lib/transport/pid_controller.h",
+    "src/core/lib/transport/service_config.h",
     "src/core/lib/transport/static_metadata.h",
     "src/core/lib/transport/timeout_encoding.h",
     "src/core/lib/transport/transport.h",
@@ -859,8 +859,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.c",
     "src/core/lib/transport/metadata.c",
     "src/core/lib/transport/metadata_batch.c",
-    "src/core/lib/transport/method_config.c",
     "src/core/lib/transport/pid_controller.c",
+    "src/core/lib/transport/service_config.c",
     "src/core/lib/transport/static_metadata.c",
     "src/core/lib/transport/timeout_encoding.c",
     "src/core/lib/transport/transport.c",
@@ -1075,8 +1075,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.h",
     "src/core/lib/transport/metadata.h",
     "src/core/lib/transport/metadata_batch.h",
-    "src/core/lib/transport/method_config.h",
     "src/core/lib/transport/pid_controller.h",
+    "src/core/lib/transport/service_config.h",
     "src/core/lib/transport/static_metadata.h",
     "src/core/lib/transport/timeout_encoding.h",
     "src/core/lib/transport/transport.h",
@@ -1242,8 +1242,8 @@ cc_library(
     "src/core/lib/transport/connectivity_state.c",
     "src/core/lib/transport/metadata.c",
     "src/core/lib/transport/metadata_batch.c",
-    "src/core/lib/transport/method_config.c",
     "src/core/lib/transport/pid_controller.c",
+    "src/core/lib/transport/service_config.c",
     "src/core/lib/transport/static_metadata.c",
     "src/core/lib/transport/timeout_encoding.c",
     "src/core/lib/transport/transport.c",
@@ -2109,8 +2109,8 @@ objc_library(
     "src/core/lib/transport/connectivity_state.c",
     "src/core/lib/transport/metadata.c",
     "src/core/lib/transport/metadata_batch.c",
-    "src/core/lib/transport/method_config.c",
     "src/core/lib/transport/pid_controller.c",
+    "src/core/lib/transport/service_config.c",
     "src/core/lib/transport/static_metadata.c",
     "src/core/lib/transport/timeout_encoding.c",
     "src/core/lib/transport/transport.c",
@@ -2334,8 +2334,8 @@ objc_library(
     "src/core/lib/transport/connectivity_state.h",
     "src/core/lib/transport/metadata.h",
     "src/core/lib/transport/metadata_batch.h",
-    "src/core/lib/transport/method_config.h",
     "src/core/lib/transport/pid_controller.h",
+    "src/core/lib/transport/service_config.h",
     "src/core/lib/transport/static_metadata.h",
     "src/core/lib/transport/timeout_encoding.h",
     "src/core/lib/transport/transport.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98bcf22978634fef640380c6bf1f06c713b57a55..bbce41bee6a9a923d9645f0b517e73d811a5a3e0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -393,8 +393,8 @@ add_library(grpc
   src/core/lib/transport/connectivity_state.c
   src/core/lib/transport/metadata.c
   src/core/lib/transport/metadata_batch.c
-  src/core/lib/transport/method_config.c
   src/core/lib/transport/pid_controller.c
+  src/core/lib/transport/service_config.c
   src/core/lib/transport/static_metadata.c
   src/core/lib/transport/timeout_encoding.c
   src/core/lib/transport/transport.c
@@ -674,8 +674,8 @@ add_library(grpc_cronet
   src/core/lib/transport/connectivity_state.c
   src/core/lib/transport/metadata.c
   src/core/lib/transport/metadata_batch.c
-  src/core/lib/transport/method_config.c
   src/core/lib/transport/pid_controller.c
+  src/core/lib/transport/service_config.c
   src/core/lib/transport/static_metadata.c
   src/core/lib/transport/timeout_encoding.c
   src/core/lib/transport/transport.c
@@ -927,8 +927,8 @@ add_library(grpc_unsecure
   src/core/lib/transport/connectivity_state.c
   src/core/lib/transport/metadata.c
   src/core/lib/transport/metadata_batch.c
-  src/core/lib/transport/method_config.c
   src/core/lib/transport/pid_controller.c
+  src/core/lib/transport/service_config.c
   src/core/lib/transport/static_metadata.c
   src/core/lib/transport/timeout_encoding.c
   src/core/lib/transport/transport.c
diff --git a/Makefile b/Makefile
index 72021517d9d65e4ae44706f3810a71ebd3914032..0e0217e4993103ae5f318a044ba13e9c2e45b4bd 100644
--- a/Makefile
+++ b/Makefile
@@ -2732,8 +2732,8 @@ LIBGRPC_SRC = \
     src/core/lib/transport/connectivity_state.c \
     src/core/lib/transport/metadata.c \
     src/core/lib/transport/metadata_batch.c \
-    src/core/lib/transport/method_config.c \
     src/core/lib/transport/pid_controller.c \
+    src/core/lib/transport/service_config.c \
     src/core/lib/transport/static_metadata.c \
     src/core/lib/transport/timeout_encoding.c \
     src/core/lib/transport/transport.c \
@@ -3031,8 +3031,8 @@ LIBGRPC_CRONET_SRC = \
     src/core/lib/transport/connectivity_state.c \
     src/core/lib/transport/metadata.c \
     src/core/lib/transport/metadata_batch.c \
-    src/core/lib/transport/method_config.c \
     src/core/lib/transport/pid_controller.c \
+    src/core/lib/transport/service_config.c \
     src/core/lib/transport/static_metadata.c \
     src/core/lib/transport/timeout_encoding.c \
     src/core/lib/transport/transport.c \
@@ -3321,8 +3321,8 @@ LIBGRPC_TEST_UTIL_SRC = \
     src/core/lib/transport/connectivity_state.c \
     src/core/lib/transport/metadata.c \
     src/core/lib/transport/metadata_batch.c \
-    src/core/lib/transport/method_config.c \
     src/core/lib/transport/pid_controller.c \
+    src/core/lib/transport/service_config.c \
     src/core/lib/transport/static_metadata.c \
     src/core/lib/transport/timeout_encoding.c \
     src/core/lib/transport/transport.c \
@@ -3540,8 +3540,8 @@ LIBGRPC_UNSECURE_SRC = \
     src/core/lib/transport/connectivity_state.c \
     src/core/lib/transport/metadata.c \
     src/core/lib/transport/metadata_batch.c \
-    src/core/lib/transport/method_config.c \
     src/core/lib/transport/pid_controller.c \
+    src/core/lib/transport/service_config.c \
     src/core/lib/transport/static_metadata.c \
     src/core/lib/transport/timeout_encoding.c \
     src/core/lib/transport/transport.c \
diff --git a/binding.gyp b/binding.gyp
index 40ed7cc9e6c1aa01c7e7c3fffbe61182dacb62c2..4e17d68588e258c6cc7f6cbd909c03181d3d9b25 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -672,8 +672,8 @@
         'src/core/lib/transport/connectivity_state.c',
         'src/core/lib/transport/metadata.c',
         'src/core/lib/transport/metadata_batch.c',
-        'src/core/lib/transport/method_config.c',
         'src/core/lib/transport/pid_controller.c',
+        'src/core/lib/transport/service_config.c',
         'src/core/lib/transport/static_metadata.c',
         'src/core/lib/transport/timeout_encoding.c',
         'src/core/lib/transport/transport.c',
diff --git a/build.yaml b/build.yaml
index 6f105096137590c6395b15a34138eaa6b46a372f..cca5003d275dbc5fab153fc1a1d03e4d5c658c4f 100644
--- a/build.yaml
+++ b/build.yaml
@@ -255,8 +255,8 @@ filegroups:
   - src/core/lib/transport/connectivity_state.h
   - src/core/lib/transport/metadata.h
   - src/core/lib/transport/metadata_batch.h
-  - src/core/lib/transport/method_config.h
   - src/core/lib/transport/pid_controller.h
+  - src/core/lib/transport/service_config.h
   - src/core/lib/transport/static_metadata.h
   - src/core/lib/transport/timeout_encoding.h
   - src/core/lib/transport/transport.h
@@ -369,8 +369,8 @@ filegroups:
   - src/core/lib/transport/connectivity_state.c
   - src/core/lib/transport/metadata.c
   - src/core/lib/transport/metadata_batch.c
-  - src/core/lib/transport/method_config.c
   - src/core/lib/transport/pid_controller.c
+  - src/core/lib/transport/service_config.c
   - src/core/lib/transport/static_metadata.c
   - src/core/lib/transport/timeout_encoding.c
   - src/core/lib/transport/transport.c
diff --git a/config.m4 b/config.m4
index 544c6f17f1e3817a02ee443414272f1025f04ba4..4c39e6397a7565378cbfcff4ea7f66c2ec47ff5d 100644
--- a/config.m4
+++ b/config.m4
@@ -188,8 +188,8 @@ if test "$PHP_GRPC" != "no"; then
     src/core/lib/transport/connectivity_state.c \
     src/core/lib/transport/metadata.c \
     src/core/lib/transport/metadata_batch.c \
-    src/core/lib/transport/method_config.c \
     src/core/lib/transport/pid_controller.c \
+    src/core/lib/transport/service_config.c \
     src/core/lib/transport/static_metadata.c \
     src/core/lib/transport/timeout_encoding.c \
     src/core/lib/transport/transport.c \
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 0ced98e64f0ba86c9c46992a7ce593bb463f9981..f865dc4657a118cb928c37fde848894d84869fd0 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -338,8 +338,8 @@ Pod::Spec.new do |s|
                       'src/core/lib/transport/connectivity_state.h',
                       'src/core/lib/transport/metadata.h',
                       'src/core/lib/transport/metadata_batch.h',
-                      'src/core/lib/transport/method_config.h',
                       'src/core/lib/transport/pid_controller.h',
+                      'src/core/lib/transport/service_config.h',
                       'src/core/lib/transport/static_metadata.h',
                       'src/core/lib/transport/timeout_encoding.h',
                       'src/core/lib/transport/transport.h',
@@ -532,8 +532,8 @@ Pod::Spec.new do |s|
                       'src/core/lib/transport/connectivity_state.c',
                       'src/core/lib/transport/metadata.c',
                       'src/core/lib/transport/metadata_batch.c',
-                      'src/core/lib/transport/method_config.c',
                       'src/core/lib/transport/pid_controller.c',
+                      'src/core/lib/transport/service_config.c',
                       'src/core/lib/transport/static_metadata.c',
                       'src/core/lib/transport/timeout_encoding.c',
                       'src/core/lib/transport/transport.c',
@@ -742,8 +742,8 @@ Pod::Spec.new do |s|
                               'src/core/lib/transport/connectivity_state.h',
                               'src/core/lib/transport/metadata.h',
                               'src/core/lib/transport/metadata_batch.h',
-                              'src/core/lib/transport/method_config.h',
                               'src/core/lib/transport/pid_controller.h',
+                              'src/core/lib/transport/service_config.h',
                               'src/core/lib/transport/static_metadata.h',
                               'src/core/lib/transport/timeout_encoding.h',
                               'src/core/lib/transport/transport.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 42196e8d240ceabc96a44d16fb300e5913c815f7..385a9f4caaf87e120f13379b34e63caeab8e2db1 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -258,8 +258,8 @@ Gem::Specification.new do |s|
   s.files += %w( src/core/lib/transport/connectivity_state.h )
   s.files += %w( src/core/lib/transport/metadata.h )
   s.files += %w( src/core/lib/transport/metadata_batch.h )
-  s.files += %w( src/core/lib/transport/method_config.h )
   s.files += %w( src/core/lib/transport/pid_controller.h )
+  s.files += %w( src/core/lib/transport/service_config.h )
   s.files += %w( src/core/lib/transport/static_metadata.h )
   s.files += %w( src/core/lib/transport/timeout_encoding.h )
   s.files += %w( src/core/lib/transport/transport.h )
@@ -452,8 +452,8 @@ Gem::Specification.new do |s|
   s.files += %w( src/core/lib/transport/connectivity_state.c )
   s.files += %w( src/core/lib/transport/metadata.c )
   s.files += %w( src/core/lib/transport/metadata_batch.c )
-  s.files += %w( src/core/lib/transport/method_config.c )
   s.files += %w( src/core/lib/transport/pid_controller.c )
+  s.files += %w( src/core/lib/transport/service_config.c )
   s.files += %w( src/core/lib/transport/static_metadata.c )
   s.files += %w( src/core/lib/transport/timeout_encoding.c )
   s.files += %w( src/core/lib/transport/transport.c )
diff --git a/include/grpc++/channel.h b/include/grpc++/channel.h
index c8360282e7b03c7979b3652f5b0329c85f0c0674..ca269be218fba5ed66d360310a9474607e723ea4 100644
--- a/include/grpc++/channel.h
+++ b/include/grpc++/channel.h
@@ -57,6 +57,13 @@ class Channel final : public ChannelInterface,
   /// \a try_to_connect is set to true, try to connect.
   grpc_connectivity_state GetState(bool try_to_connect) override;
 
+  /// Returns the LB policy name, or the empty string if not yet available.
+  grpc::string GetLoadBalancingPolicyName() const;
+
+  /// Returns the service config in JSON form, or the empty string if
+  /// not available.
+  grpc::string GetServiceConfigJSON() const;
+
  private:
   template <class InputMessage, class OutputMessage>
   friend Status BlockingUnaryCall(ChannelInterface* channel,
diff --git a/include/grpc++/support/channel_arguments.h b/include/grpc++/support/channel_arguments.h
index 220b6ef1ddcfcaa37d1af7a27c34b1ebf0b88bee..d43f7c61bddbef307afab7f9d929e30d0b4355be 100644
--- a/include/grpc++/support/channel_arguments.h
+++ b/include/grpc++/support/channel_arguments.h
@@ -93,6 +93,10 @@ class ChannelArguments {
   /// grpclb LB policy will be used, regardless of what is specified here.
   void SetLoadBalancingPolicyName(const grpc::string& lb_policy_name);
 
+  /// Set service config in JSON form.
+  /// Primarily meant for use in unit tests.
+  void SetServiceConfigJSON(const grpc::string& service_config_json);
+
   // Generic channel argument setters. Only for advanced use cases.
   /// Set an integer argument \a value under \a key.
   void SetInt(const grpc::string& key, int value);
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 1d9402cf33a530554d6cfbf63b3bad7a2f493b77..5170621bbb9a8ee57ab4f8f7c32d2802c82e8e66 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -457,6 +457,9 @@ typedef struct {
   /* If non-NULL, will be set to point to a string indicating the LB
    * policy name.  Caller takes ownership. */
   char **lb_policy_name;
+  /* If non-NULL, will be set to point to a string containing the
+   * service config used by the channel in JSON form. */
+  char **service_config_json;
 } grpc_channel_info;
 
 typedef struct grpc_resource_quota grpc_resource_quota;
diff --git a/package.xml b/package.xml
index e4397eddea6451577d5491778ce7fe84ff83af68..ff84a0e45bc131b12309d33a6095bc787314d259 100644
--- a/package.xml
+++ b/package.xml
@@ -265,8 +265,8 @@
     <file baseinstalldir="/" name="src/core/lib/transport/connectivity_state.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/metadata.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/metadata_batch.h" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/transport/method_config.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/pid_controller.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/transport/service_config.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/static_metadata.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/timeout_encoding.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/transport.h" role="src" />
@@ -459,8 +459,8 @@
     <file baseinstalldir="/" name="src/core/lib/transport/connectivity_state.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/metadata.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/metadata_batch.c" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/transport/method_config.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/pid_controller.c" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/transport/service_config.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/static_metadata.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/timeout_encoding.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/transport/transport.c" role="src" />
diff --git a/src/core/ext/client_channel/client_channel.c b/src/core/ext/client_channel/client_channel.c
index 5faad33a1d1c53481af44cc7f6356cb7fb045886..8929e8c48a77532bb20fdf3152015c696b477f28 100644
--- a/src/core/ext/client_channel/client_channel.c
+++ b/src/core/ext/client_channel/client_channel.c
@@ -57,7 +57,7 @@
 #include "src/core/lib/transport/connectivity_state.h"
 #include "src/core/lib/transport/metadata.h"
 #include "src/core/lib/transport/metadata_batch.h"
-#include "src/core/lib/transport/method_config.h"
+#include "src/core/lib/transport/service_config.h"
 #include "src/core/lib/transport/static_metadata.h"
 
 /* Client channel implementation */
@@ -83,34 +83,65 @@ static void *method_parameters_copy(void *value) {
   return new_value;
 }
 
-static int method_parameters_cmp(void *value1, void *value2) {
-  const method_parameters *v1 = value1;
-  const method_parameters *v2 = value2;
-  const int retval = gpr_time_cmp(v1->timeout, v2->timeout);
-  if (retval != 0) return retval;
-  if (v1->wait_for_ready > v2->wait_for_ready) return 1;
-  if (v1->wait_for_ready < v2->wait_for_ready) return -1;
-  return 0;
-}
-
-static void method_parameters_del(grpc_exec_ctx *exec_ctx, void *p) {
+static void method_parameters_free(grpc_exec_ctx *exec_ctx, void *p) {
   gpr_free(p);
 }
 
 static const grpc_slice_hash_table_vtable method_parameters_vtable = {
-    method_parameters_del, method_parameters_copy, method_parameters_cmp};
-
-static void *method_config_convert_value(
-    const grpc_method_config *method_config) {
+    method_parameters_free, method_parameters_copy};
+
+static void *method_parameters_create_from_json(const grpc_json *json) {
+  wait_for_ready_value wait_for_ready = WAIT_FOR_READY_UNSET;
+  gpr_timespec timeout = {0, 0, GPR_TIMESPAN};
+  for (grpc_json *field = json->child; field != NULL; field = field->next) {
+    if (field->key == NULL) continue;
+    if (strcmp(field->key, "waitForReady") == 0) {
+      if (wait_for_ready != WAIT_FOR_READY_UNSET) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_TRUE && field->type != GRPC_JSON_FALSE) {
+        return NULL;
+      }
+      wait_for_ready = field->type == GRPC_JSON_TRUE ? WAIT_FOR_READY_TRUE
+                                                     : WAIT_FOR_READY_FALSE;
+    } else if (strcmp(field->key, "timeout") == 0) {
+      if (timeout.tv_sec > 0 || timeout.tv_nsec > 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      size_t len = strlen(field->value);
+      if (field->value[len - 1] != 's') return NULL;
+      char *buf = gpr_strdup(field->value);
+      buf[len - 1] = '\0';  // Remove trailing 's'.
+      char *decimal_point = strchr(buf, '.');
+      if (decimal_point != NULL) {
+        *decimal_point = '\0';
+        timeout.tv_nsec = gpr_parse_nonnegative_int(decimal_point + 1);
+        if (timeout.tv_nsec == -1) {
+          gpr_free(buf);
+          return NULL;
+        }
+        // There should always be exactly 3, 6, or 9 fractional digits.
+        int multiplier = 1;
+        switch (strlen(decimal_point + 1)) {
+          case 9:
+            break;
+          case 6:
+            multiplier *= 1000;
+            break;
+          case 3:
+            multiplier *= 1000000;
+            break;
+          default:  // Unsupported number of digits.
+            gpr_free(buf);
+            return NULL;
+        }
+        timeout.tv_nsec *= multiplier;
+      }
+      timeout.tv_sec = gpr_parse_nonnegative_int(buf);
+      if (timeout.tv_sec == -1) return NULL;
+      gpr_free(buf);
+    }
+  }
   method_parameters *value = gpr_malloc(sizeof(method_parameters));
-  const gpr_timespec *timeout = grpc_method_config_get_timeout(method_config);
-  value->timeout = timeout != NULL ? *timeout : gpr_time_0(GPR_TIMESPAN);
-  const bool *wait_for_ready =
-      grpc_method_config_get_wait_for_ready(method_config);
-  value->wait_for_ready =
-      wait_for_ready == NULL
-          ? WAIT_FOR_READY_UNSET
-          : (wait_for_ready ? WAIT_FOR_READY_TRUE : WAIT_FOR_READY_FALSE);
+  value->timeout = timeout;
+  value->wait_for_ready = wait_for_ready;
   return value;
 }
 
@@ -131,6 +162,8 @@ typedef struct client_channel_channel_data {
   /** currently active load balancer */
   char *lb_policy_name;
   grpc_lb_policy *lb_policy;
+  /** service config in JSON form */
+  char *service_config_json;
   /** maps method names to method_parameters structs */
   grpc_slice_hash_table *method_params_table;
   /** incoming resolver result - set by resolver.next() */
@@ -237,15 +270,12 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
   grpc_connectivity_state state = GRPC_CHANNEL_TRANSIENT_FAILURE;
   bool exit_idle = false;
   grpc_error *state_error = GRPC_ERROR_CREATE("No load balancing policy");
+  char *service_config_json = NULL;
 
   if (chand->resolver_result != NULL) {
-    grpc_lb_policy_args lb_policy_args;
-    lb_policy_args.args = chand->resolver_result;
-    lb_policy_args.client_channel_factory = chand->client_channel_factory;
-
     // Find LB policy name.
     const grpc_arg *channel_arg =
-        grpc_channel_args_find(lb_policy_args.args, GRPC_ARG_LB_POLICY_NAME);
+        grpc_channel_args_find(chand->resolver_result, GRPC_ARG_LB_POLICY_NAME);
     if (channel_arg != NULL) {
       GPR_ASSERT(channel_arg->type == GRPC_ARG_STRING);
       lb_policy_name = channel_arg->value.string;
@@ -254,7 +284,7 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
     // assume that we should use the grpclb policy, regardless of what the
     // resolver actually specified.
     channel_arg =
-        grpc_channel_args_find(lb_policy_args.args, GRPC_ARG_LB_ADDRESSES);
+        grpc_channel_args_find(chand->resolver_result, GRPC_ARG_LB_ADDRESSES);
     if (channel_arg != NULL) {
       GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
       grpc_lb_addresses *addresses = channel_arg->value.pointer.p;
@@ -279,7 +309,10 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
     // Use pick_first if nothing was specified and we didn't select grpclb
     // above.
     if (lb_policy_name == NULL) lb_policy_name = "pick_first";
-
+    // Instantiate LB policy.
+    grpc_lb_policy_args lb_policy_args;
+    lb_policy_args.args = chand->resolver_result;
+    lb_policy_args.client_channel_factory = chand->client_channel_factory;
     lb_policy =
         grpc_lb_policy_create(exec_ctx, lb_policy_name, &lb_policy_args);
     if (lb_policy != NULL) {
@@ -288,13 +321,20 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
       state =
           grpc_lb_policy_check_connectivity(exec_ctx, lb_policy, &state_error);
     }
+    // Find service config.
     channel_arg =
-        grpc_channel_args_find(lb_policy_args.args, GRPC_ARG_SERVICE_CONFIG);
+        grpc_channel_args_find(chand->resolver_result, GRPC_ARG_SERVICE_CONFIG);
     if (channel_arg != NULL) {
-      GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
-      method_params_table = grpc_method_config_table_convert(
-          exec_ctx, (grpc_method_config_table *)channel_arg->value.pointer.p,
-          method_config_convert_value, &method_parameters_vtable);
+      GPR_ASSERT(channel_arg->type == GRPC_ARG_STRING);
+      service_config_json = gpr_strdup(channel_arg->value.string);
+      grpc_service_config *service_config =
+          grpc_service_config_create(service_config_json);
+      if (service_config != NULL) {
+        method_params_table = grpc_service_config_create_method_config_table(
+            exec_ctx, service_config, method_parameters_create_from_json,
+            &method_parameters_vtable);
+        grpc_service_config_destroy(service_config);
+      }
     }
     // Before we clean up, save a copy of lb_policy_name, since it might
     // be pointing to data inside chand->resolver_result.
@@ -316,6 +356,10 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
   }
   old_lb_policy = chand->lb_policy;
   chand->lb_policy = lb_policy;
+  if (service_config_json != NULL) {
+    gpr_free(chand->service_config_json);
+    chand->service_config_json = service_config_json;
+  }
   if (chand->method_params_table != NULL) {
     grpc_slice_hash_table_unref(exec_ctx, chand->method_params_table);
   }
@@ -451,6 +495,11 @@ static void cc_get_channel_info(grpc_exec_ctx *exec_ctx,
                                 ? NULL
                                 : gpr_strdup(chand->lb_policy_name);
   }
+  if (info->service_config_json != NULL) {
+    *info->service_config_json = chand->service_config_json == NULL
+                                     ? NULL
+                                     : gpr_strdup(chand->service_config_json);
+  }
   gpr_mu_unlock(&chand->mu);
 }
 
@@ -494,6 +543,7 @@ static void cc_destroy_channel_elem(grpc_exec_ctx *exec_ctx,
     GRPC_LB_POLICY_UNREF(exec_ctx, chand->lb_policy, "channel");
   }
   gpr_free(chand->lb_policy_name);
+  gpr_free(chand->service_config_json);
   if (chand->method_params_table != NULL) {
     grpc_slice_hash_table_unref(exec_ctx, chand->method_params_table);
   }
diff --git a/src/core/ext/client_channel/lb_policy_registry.c b/src/core/ext/client_channel/lb_policy_registry.c
index f46a721f9d4eda22ac9bde7105d9b9c8f14e2c7e..90c149d947b2e06c22ad00431c906d0b88f6db0c 100644
--- a/src/core/ext/client_channel/lb_policy_registry.c
+++ b/src/core/ext/client_channel/lb_policy_registry.c
@@ -35,6 +35,8 @@
 
 #include <string.h>
 
+#include "src/core/lib/support/string.h"
+
 #define MAX_POLICIES 10
 
 static grpc_lb_policy_factory *g_all_of_the_lb_policies[MAX_POLICIES];
@@ -52,8 +54,8 @@ void grpc_lb_policy_registry_shutdown(void) {
 void grpc_register_lb_policy(grpc_lb_policy_factory *factory) {
   int i;
   for (i = 0; i < g_number_of_lb_policies; i++) {
-    GPR_ASSERT(0 != strcmp(factory->vtable->name,
-                           g_all_of_the_lb_policies[i]->vtable->name));
+    GPR_ASSERT(0 != gpr_stricmp(factory->vtable->name,
+                                g_all_of_the_lb_policies[i]->vtable->name));
   }
   GPR_ASSERT(g_number_of_lb_policies != MAX_POLICIES);
   grpc_lb_policy_factory_ref(factory);
@@ -66,7 +68,7 @@ static grpc_lb_policy_factory *lookup_factory(const char *name) {
   if (name == NULL) return NULL;
 
   for (i = 0; i < g_number_of_lb_policies; i++) {
-    if (0 == strcmp(name, g_all_of_the_lb_policies[i]->vtable->name)) {
+    if (0 == gpr_stricmp(name, g_all_of_the_lb_policies[i]->vtable->name)) {
       return g_all_of_the_lb_policies[i];
     }
   }
diff --git a/src/core/ext/lb_policy/grpclb/grpclb.c b/src/core/ext/lb_policy/grpclb/grpclb.c
index 7bc9804e6397908be041c07339018915d8177db6..b10b980331222a0d948ba451ef30896a56051eaa 100644
--- a/src/core/ext/lb_policy/grpclb/grpclb.c
+++ b/src/core/ext/lb_policy/grpclb/grpclb.c
@@ -137,7 +137,7 @@ static void initial_metadata_add_lb_token(
     grpc_metadata_batch *initial_metadata,
     grpc_linked_mdelem *lb_token_mdelem_storage, grpc_mdelem lb_token) {
   GPR_ASSERT(lb_token_mdelem_storage != NULL);
-  GPR_ASSERT(lb_token != NULL);
+  GPR_ASSERT(!GRPC_MDISNULL(lb_token));
   grpc_metadata_batch_add_tail(initial_metadata, lb_token_mdelem_storage,
                                lb_token);
 }
@@ -187,14 +187,20 @@ static void wrapped_rr_closure(grpc_exec_ctx *exec_ctx, void *arg,
      * addresses failed to connect). There won't be any user_data/token
      * available */
     if (wc_arg->target != NULL) {
-      GPR_ASSERT(wc_arg->lb_token != NULL);
-      initial_metadata_add_lb_token(wc_arg->initial_metadata,
-                                    wc_arg->lb_token_mdelem_storage,
-                                    GRPC_MDELEM_REF(wc_arg->lb_token));
+      if (!GRPC_MDISNULL(wc_arg->lb_token)) {
+        initial_metadata_add_lb_token(wc_arg->initial_metadata,
+                                      wc_arg->lb_token_mdelem_storage,
+                                      GRPC_MDELEM_REF(wc_arg->lb_token));
+      } else {
+        gpr_log(GPR_ERROR,
+                "No LB token for connected subchannel pick %p (from RR "
+                "instance %p).",
+                (void *)*wc_arg->target, (void *)wc_arg->rr_policy);
+        abort();
+      }
     }
     if (grpc_lb_glb_trace) {
-      gpr_log(GPR_INFO, "Unreffing RR (0x%" PRIxPTR ")",
-              (intptr_t)wc_arg->rr_policy);
+      gpr_log(GPR_INFO, "Unreffing RR %p", (void *)wc_arg->rr_policy);
     }
     GRPC_LB_POLICY_UNREF(exec_ctx, wc_arg->rr_policy, "wrapped_rr_closure");
   }
@@ -375,10 +381,10 @@ static bool is_server_valid(const grpc_grpclb_server *server, size_t idx,
 
 /* vtable for LB tokens in grpc_lb_addresses. */
 static void *lb_token_copy(void *token) {
-  return token == NULL ? NULL : GRPC_MDELEM_REF(token);
+  return token == NULL ? NULL : GRPC_MDELEM_REF((grpc_mdelem){token}).payload;
 }
 static void lb_token_destroy(grpc_exec_ctx *exec_ctx, void *token) {
-  if (token != NULL) GRPC_MDELEM_UNREF(exec_ctx, token);
+  if (token != NULL) GRPC_MDELEM_UNREF(exec_ctx, (grpc_mdelem){token});
 }
 static int lb_token_cmp(void *token1, void *token2) {
   if (token1 > token2) return 1;
@@ -411,7 +417,7 @@ static void parse_server(const grpc_grpclb_server *server,
 }
 
 /* Returns addresses extracted from \a serverlist. */
-static grpc_lb_addresses *process_serverlist(
+static grpc_lb_addresses *process_serverlist_locked(
     grpc_exec_ctx *exec_ctx, const grpc_grpclb_serverlist *serverlist) {
   size_t num_valid = 0;
   /* first pass: count how many are valid in order to allocate the necessary
@@ -448,14 +454,17 @@ static grpc_lb_addresses *process_serverlist(
           strnlen(server->load_balance_token, lb_token_max_length);
       grpc_slice lb_token_mdstr = grpc_slice_from_copied_buffer(
           server->load_balance_token, lb_token_length);
-      user_data = grpc_mdelem_from_slices(exec_ctx, GRPC_MDSTR_LB_TOKEN,
-                                          lb_token_mdstr);
+      user_data =
+          grpc_mdelem_from_slices(exec_ctx, GRPC_MDSTR_LB_TOKEN, lb_token_mdstr)
+              .payload;
     } else {
-      gpr_log(GPR_ERROR,
+      char *uri = grpc_sockaddr_to_uri(&addr);
+      gpr_log(GPR_INFO,
               "Missing LB token for backend address '%s'. The empty token will "
               "be used instead",
-              grpc_sockaddr_to_uri(&addr));
-      user_data = GRPC_MDELEM_LB_TOKEN_EMPTY;
+              uri);
+      gpr_free(uri);
+      user_data = GRPC_MDELEM_LB_TOKEN_EMPTY.payload;
     }
 
     grpc_lb_addresses_set_address(lb_addresses, addr_idx, &addr.addr, addr.len,
@@ -508,7 +517,8 @@ static grpc_lb_policy *create_rr_locked(
   grpc_lb_policy_args args;
   memset(&args, 0, sizeof(args));
   args.client_channel_factory = glb_policy->cc_factory;
-  grpc_lb_addresses *addresses = process_serverlist(exec_ctx, serverlist);
+  grpc_lb_addresses *addresses =
+      process_serverlist_locked(exec_ctx, serverlist);
 
   // Replace the LB addresses in the channel args that we pass down to
   // the subchannel.
@@ -768,7 +778,6 @@ static void glb_shutdown(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) {
    * while holding glb_policy->mu: lb_on_server_status_received, invoked due to
    * the cancel, needs to acquire that same lock */
   grpc_call *lb_call = glb_policy->lb_call;
-  glb_policy->lb_call = NULL;
   gpr_mu_unlock(&glb_policy->mu);
 
   /* glb_policy->lb_call and this local lb_call must be consistent at this point
diff --git a/src/core/ext/load_reporting/load_reporting_filter.c b/src/core/ext/load_reporting/load_reporting_filter.c
index 1187810e26a54e1bb8f8576ec991b5a09eadd31c..5d3b4c4207dd5cec1890ee1f21fbcdce8e71aa53 100644
--- a/src/core/ext/load_reporting/load_reporting_filter.c
+++ b/src/core/ext/load_reporting/load_reporting_filter.c
@@ -78,13 +78,13 @@ static grpc_mdelem recv_md_filter(grpc_exec_ctx *exec_ctx, void *user_data,
   grpc_call_element *elem = a->elem;
   call_data *calld = elem->call_data;
 
-  if (grpc_slice_cmp(md->key, GRPC_MDSTR_PATH) == 0) {
-    calld->service_method = grpc_slice_ref_internal(md->value);
+  if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_PATH) == 0) {
+    calld->service_method = grpc_slice_ref_internal(GRPC_MDVALUE(md));
     calld->have_service_method = true;
-  } else if (grpc_slice_cmp(md->key, GRPC_MDSTR_LB_TOKEN) == 0) {
-    calld->initial_md_string = grpc_slice_ref_internal(md->value);
+  } else if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_LB_TOKEN) == 0) {
+    calld->initial_md_string = grpc_slice_ref_internal(GRPC_MDVALUE(md));
     calld->have_initial_md_string = true;
-    return NULL;
+    return GRPC_MDNULL;
   }
 
   return md;
@@ -201,10 +201,10 @@ static grpc_mdelem lr_trailing_md_filter(grpc_exec_ctx *exec_ctx,
   grpc_call_element *elem = user_data;
   call_data *calld = elem->call_data;
 
-  if (grpc_slice_cmp(md->key, GRPC_MDSTR_LB_COST_BIN) == 0) {
-    calld->trailing_md_string = grpc_slice_ref_internal(md->value);
+  if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_LB_COST_BIN) == 0) {
+    calld->trailing_md_string = grpc_slice_ref_internal(GRPC_MDVALUE(md));
     calld->have_trailing_md_string = true;
-    return NULL;
+    return GRPC_MDNULL;
   }
 
   return md;
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index fac9a0858448720601687837894c79b2b1c046f1..91861829e3de60231cb6cfe6d840c3558fa0f44d 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -876,8 +876,8 @@ void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx,
 static bool contains_non_ok_status(grpc_metadata_batch *batch) {
   grpc_linked_mdelem *l;
   for (l = batch->list.head; l; l = l->next) {
-    if (grpc_slice_cmp(l->md->key, GRPC_MDSTR_GRPC_STATUS) == 0 &&
-        l->md != GRPC_MDELEM_GRPC_STATUS_0) {
+    if (grpc_slice_cmp(GRPC_MDKEY(l->md), GRPC_MDSTR_GRPC_STATUS) == 0 &&
+        !grpc_mdelem_eq(l->md, GRPC_MDELEM_GRPC_STATUS_0)) {
       return true;
     }
   }
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.c b/src/core/ext/transport/chttp2/transport/hpack_encoder.c
index 9a0173b7b7f8e87f59a5228727f0af114efcbe4d..8e2264a60290cb92635200368aab6b65065c160d 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.c
@@ -190,8 +190,8 @@ static void evict_entry(grpc_chttp2_hpack_compressor *c) {
 /* add an element to the decoder table */
 static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
                      grpc_mdelem elem) {
-  uint32_t key_hash = grpc_slice_hash(elem->key);
-  uint32_t value_hash = grpc_slice_hash(elem->value);
+  uint32_t key_hash = grpc_slice_hash(GRPC_MDKEY(elem));
+  uint32_t value_hash = grpc_slice_hash(GRPC_MDVALUE(elem));
   uint32_t elem_hash = GRPC_MDSTR_KV_HASH(key_hash, value_hash);
   uint32_t new_index = c->tail_remote_index + c->table_elems + 1;
   size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem);
@@ -217,17 +217,18 @@ static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
   c->table_elems++;
 
   /* Store this element into {entries,indices}_elem */
-  if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == elem) {
+  if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem)) {
     /* already there: update with new index */
     c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
-  } else if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == elem) {
+  } else if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)],
+                            elem)) {
     /* already there (cuckoo): update with new index */
     c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
-  } else if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == NULL) {
+  } else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_2(elem_hash)])) {
     /* not there, but a free element: add */
     c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = GRPC_MDELEM_REF(elem);
     c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
-  } else if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == NULL) {
+  } else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_3(elem_hash)])) {
     /* not there (cuckoo), but a free element: add */
     c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = GRPC_MDELEM_REF(elem);
     c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
@@ -246,34 +247,34 @@ static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
 
   /* do exactly the same for the key (so we can find by that again too) */
 
-  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_2(key_hash)], elem->key) ==
-      0) {
+  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_2(key_hash)],
+                     GRPC_MDKEY(elem)) == 0) {
     c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
   } else if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_3(key_hash)],
-                            elem->key) == 0) {
+                            GRPC_MDKEY(elem)) == 0) {
     c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
   } else if (c->entries_keys[HASH_FRAGMENT_2(key_hash)].refcount ==
              &terminal_slice_refcount) {
     c->entries_keys[HASH_FRAGMENT_2(key_hash)] =
-        grpc_slice_ref_internal(elem->key);
+        grpc_slice_ref_internal(GRPC_MDKEY(elem));
     c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
   } else if (c->entries_keys[HASH_FRAGMENT_3(key_hash)].refcount ==
              &terminal_slice_refcount) {
     c->entries_keys[HASH_FRAGMENT_3(key_hash)] =
-        grpc_slice_ref_internal(elem->key);
+        grpc_slice_ref_internal(GRPC_MDKEY(elem));
     c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
   } else if (c->indices_keys[HASH_FRAGMENT_2(key_hash)] <
              c->indices_keys[HASH_FRAGMENT_3(key_hash)]) {
     grpc_slice_unref_internal(exec_ctx,
                               c->entries_keys[HASH_FRAGMENT_2(key_hash)]);
     c->entries_keys[HASH_FRAGMENT_2(key_hash)] =
-        grpc_slice_ref_internal(elem->key);
+        grpc_slice_ref_internal(GRPC_MDKEY(elem));
     c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
   } else {
     grpc_slice_unref_internal(exec_ctx,
                               c->entries_keys[HASH_FRAGMENT_3(key_hash)]);
     c->entries_keys[HASH_FRAGMENT_3(key_hash)] =
-        grpc_slice_ref_internal(elem->key);
+        grpc_slice_ref_internal(GRPC_MDKEY(elem));
     c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
   }
 }
@@ -286,13 +287,13 @@ static void emit_indexed(grpc_chttp2_hpack_compressor *c, uint32_t elem_index,
 }
 
 static grpc_slice get_wire_value(grpc_mdelem elem, uint8_t *huffman_prefix) {
-  if (grpc_is_binary_header(elem->key)) {
+  if (grpc_is_binary_header(GRPC_MDKEY(elem))) {
     *huffman_prefix = 0x80;
-    return grpc_chttp2_base64_encode_and_huffman_compress(elem->value);
+    return grpc_chttp2_base64_encode_and_huffman_compress(GRPC_MDVALUE(elem));
   }
   /* TODO(ctiller): opportunistically compress non-binary headers */
   *huffman_prefix = 0x00;
-  return grpc_slice_ref(elem->value);
+  return grpc_slice_ref(GRPC_MDVALUE(elem));
 }
 
 static void emit_lithdr_incidx(grpc_chttp2_hpack_compressor *c,
@@ -331,7 +332,7 @@ static void emit_lithdr_noidx(grpc_chttp2_hpack_compressor *c,
 
 static void emit_lithdr_incidx_v(grpc_chttp2_hpack_compressor *c,
                                  grpc_mdelem elem, framer_state *st) {
-  uint32_t len_key = (uint32_t)GRPC_SLICE_LENGTH(elem->key);
+  uint32_t len_key = (uint32_t)GRPC_SLICE_LENGTH(GRPC_MDKEY(elem));
   uint8_t huffman_prefix;
   grpc_slice value_slice = get_wire_value(elem, &huffman_prefix);
   uint32_t len_val = (uint32_t)GRPC_SLICE_LENGTH(value_slice);
@@ -342,7 +343,7 @@ static void emit_lithdr_incidx_v(grpc_chttp2_hpack_compressor *c,
   *add_tiny_header_data(st, 1) = 0x40;
   GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00,
                            add_tiny_header_data(st, len_key_len), len_key_len);
-  add_header_data(st, grpc_slice_ref_internal(elem->key));
+  add_header_data(st, grpc_slice_ref_internal(GRPC_MDKEY(elem)));
   GRPC_CHTTP2_WRITE_VARINT(len_val, 1, huffman_prefix,
                            add_tiny_header_data(st, len_val_len), len_val_len);
   add_header_data(st, value_slice);
@@ -350,7 +351,7 @@ static void emit_lithdr_incidx_v(grpc_chttp2_hpack_compressor *c,
 
 static void emit_lithdr_noidx_v(grpc_chttp2_hpack_compressor *c,
                                 grpc_mdelem elem, framer_state *st) {
-  uint32_t len_key = (uint32_t)GRPC_SLICE_LENGTH(elem->key);
+  uint32_t len_key = (uint32_t)GRPC_SLICE_LENGTH(GRPC_MDKEY(elem));
   uint8_t huffman_prefix;
   grpc_slice value_slice = get_wire_value(elem, &huffman_prefix);
   uint32_t len_val = (uint32_t)GRPC_SLICE_LENGTH(value_slice);
@@ -361,7 +362,7 @@ static void emit_lithdr_noidx_v(grpc_chttp2_hpack_compressor *c,
   *add_tiny_header_data(st, 1) = 0x00;
   GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00,
                            add_tiny_header_data(st, len_key_len), len_key_len);
-  add_header_data(st, grpc_slice_ref_internal(elem->key));
+  add_header_data(st, grpc_slice_ref_internal(GRPC_MDKEY(elem)));
   GRPC_CHTTP2_WRITE_VARINT(len_val, 1, huffman_prefix,
                            add_tiny_header_data(st, len_val_len), len_val_len);
   add_header_data(st, value_slice);
@@ -383,15 +384,15 @@ static uint32_t dynidx(grpc_chttp2_hpack_compressor *c, uint32_t elem_index) {
 /* encode an mdelem */
 static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
                       grpc_mdelem elem, framer_state *st) {
-  uint32_t key_hash = grpc_slice_hash(elem->key);
-  uint32_t value_hash = grpc_slice_hash(elem->value);
+  uint32_t key_hash = grpc_slice_hash(GRPC_MDKEY(elem));
+  uint32_t value_hash = grpc_slice_hash(GRPC_MDVALUE(elem));
   uint32_t elem_hash = GRPC_MDSTR_KV_HASH(key_hash, value_hash);
   size_t decoder_space_usage;
   uint32_t indices_key;
   int should_add_elem;
 
-  GPR_ASSERT(GRPC_SLICE_LENGTH(elem->key) > 0);
-  if (GRPC_SLICE_START_PTR(elem->key)[0] != ':') { /* regular header */
+  GPR_ASSERT(GRPC_SLICE_LENGTH(GRPC_MDKEY(elem)) > 0);
+  if (GRPC_SLICE_START_PTR(GRPC_MDKEY(elem))[0] != ':') { /* regular header */
     st->seen_regular_header = 1;
   } else {
     GPR_ASSERT(
@@ -403,7 +404,7 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
 
   /* is this elem currently in the decoders table? */
 
-  if (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == elem &&
+  if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem) &&
       c->indices_elems[HASH_FRAGMENT_2(elem_hash)] > c->tail_remote_index) {
     /* HIT: complete element (first cuckoo hash) */
     emit_indexed(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_2(elem_hash)]),
@@ -411,7 +412,7 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
     return;
   }
 
-  if (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == elem &&
+  if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)], elem) &&
       c->indices_elems[HASH_FRAGMENT_3(elem_hash)] > c->tail_remote_index) {
     /* HIT: complete element (second cuckoo hash) */
     emit_indexed(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_3(elem_hash)]),
@@ -428,8 +429,8 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
   /* no hits for the elem... maybe there's a key? */
 
   indices_key = c->indices_keys[HASH_FRAGMENT_2(key_hash)];
-  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_2(key_hash)], elem->key) ==
-          0 &&
+  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_2(key_hash)],
+                     GRPC_MDKEY(elem)) == 0 &&
       indices_key > c->tail_remote_index) {
     /* HIT: key (first cuckoo hash) */
     if (should_add_elem) {
@@ -444,8 +445,8 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
   }
 
   indices_key = c->indices_keys[HASH_FRAGMENT_3(key_hash)];
-  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_3(key_hash)], elem->key) ==
-          0 &&
+  if (grpc_slice_cmp(c->entries_keys[HASH_FRAGMENT_3(key_hash)],
+                     GRPC_MDKEY(elem)) == 0 &&
       indices_key > c->tail_remote_index) {
     /* HIT: key (first cuckoo hash) */
     if (should_add_elem) {
@@ -512,9 +513,7 @@ void grpc_chttp2_hpack_compressor_destroy(grpc_exec_ctx *exec_ctx,
     if (c->entries_keys[i].refcount != &terminal_slice_refcount) {
       grpc_slice_unref_internal(exec_ctx, c->entries_keys[i]);
     }
-    if (c->entries_elems[i]) {
-      GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[i]);
-    }
+    GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[i]);
   }
   gpr_free(c->table_elem_size);
 }
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.c b/src/core/ext/transport/chttp2/transport/hpack_parser.c
index d3114f5e077aeba35c51b3ceb78c9d4482c9ac26..91bedcf7f0accfdca64c2b44d3c826724620e892 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.c
@@ -773,7 +773,7 @@ static grpc_error *finish_indexed_field(grpc_exec_ctx *exec_ctx,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
-  if (md == NULL) {
+  if (GRPC_MDISNULL(md)) {
     return grpc_error_set_int(
         grpc_error_set_int(GRPC_ERROR_CREATE("Invalid HPACK index received"),
                            GRPC_ERROR_INT_INDEX, (intptr_t)p->index),
@@ -815,12 +815,12 @@ static grpc_error *finish_lithdr_incidx(grpc_exec_ctx *exec_ctx,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
-  GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(exec_ctx, p,
-             grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(md->key),
-                                     take_string(p, &p->value)),
-             1);
+  GPR_ASSERT(!GRPC_MDISNULL(md)); /* handled in string parsing */
+  grpc_error *err = on_hdr(
+      exec_ctx, p,
+      grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(GRPC_MDKEY(md)),
+                              take_string(p, &p->value)),
+      1);
   if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
   return parse_begin(exec_ctx, p, cur, end);
 }
@@ -884,12 +884,12 @@ static grpc_error *finish_lithdr_notidx(grpc_exec_ctx *exec_ctx,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
-  GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(exec_ctx, p,
-             grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(md->key),
-                                     take_string(p, &p->value)),
-             0);
+  GPR_ASSERT(!GRPC_MDISNULL(md)); /* handled in string parsing */
+  grpc_error *err = on_hdr(
+      exec_ctx, p,
+      grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(GRPC_MDKEY(md)),
+                              take_string(p, &p->value)),
+      0);
   if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
   return parse_begin(exec_ctx, p, cur, end);
 }
@@ -953,12 +953,12 @@ static grpc_error *finish_lithdr_nvridx(grpc_exec_ctx *exec_ctx,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
-  GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(exec_ctx, p,
-             grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(md->key),
-                                     take_string(p, &p->value)),
-             0);
+  GPR_ASSERT(!GRPC_MDISNULL(md)); /* handled in string parsing */
+  grpc_error *err = on_hdr(
+      exec_ctx, p,
+      grpc_mdelem_from_slices(exec_ctx, grpc_slice_ref_internal(GRPC_MDKEY(md)),
+                              take_string(p, &p->value)),
+      0);
   if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
   return parse_begin(exec_ctx, p, cur, end);
 }
@@ -1501,13 +1501,13 @@ static bool is_binary_literal_header(grpc_chttp2_hpack_parser *p) {
 static grpc_error *is_binary_indexed_header(grpc_chttp2_hpack_parser *p,
                                             bool *is) {
   grpc_mdelem elem = grpc_chttp2_hptbl_lookup(&p->table, p->index);
-  if (!elem) {
+  if (GRPC_MDISNULL(elem)) {
     return grpc_error_set_int(
         grpc_error_set_int(GRPC_ERROR_CREATE("Invalid HPACK index received"),
                            GRPC_ERROR_INT_INDEX, (intptr_t)p->index),
         GRPC_ERROR_INT_SIZE, (intptr_t)p->table.num_ents);
   }
-  *is = grpc_is_binary_header(elem->key);
+  *is = grpc_is_binary_header(GRPC_MDKEY(elem));
   return GRPC_ERROR_NONE;
 }
 
diff --git a/src/core/ext/transport/chttp2/transport/hpack_table.c b/src/core/ext/transport/chttp2/transport/hpack_table.c
index 2be0406015502d90f26bbbf5c30798d98c47f59a..2cb816fe539a67fcf50b303dc18786882ce9c5d2 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_table.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_table.c
@@ -212,7 +212,7 @@ void grpc_chttp2_hptbl_destroy(grpc_exec_ctx *exec_ctx,
 }
 
 grpc_mdelem grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl,
-                                      uint32_t tbl_index) {
+                                     uint32_t tbl_index) {
   /* Static table comes first, just return an entry from it */
   if (tbl_index <= GRPC_CHTTP2_LAST_STATIC_ENTRY) {
     return tbl->static_ents[tbl_index - 1];
@@ -225,14 +225,14 @@ grpc_mdelem grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl,
     return tbl->ents[offset];
   }
   /* Invalid entry: return error */
-  return NULL;
+  return GRPC_MDNULL;
 }
 
 /* Evict one element from the table */
 static void evict1(grpc_exec_ctx *exec_ctx, grpc_chttp2_hptbl *tbl) {
   grpc_mdelem first_ent = tbl->ents[tbl->first_ent];
-  size_t elem_bytes = GRPC_SLICE_LENGTH(first_ent->key) +
-                      GRPC_SLICE_LENGTH(first_ent->value) +
+  size_t elem_bytes = GRPC_SLICE_LENGTH(GRPC_MDKEY(first_ent)) +
+                      GRPC_SLICE_LENGTH(GRPC_MDVALUE(first_ent)) +
                       GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD;
   GPR_ASSERT(elem_bytes <= tbl->mem_used);
   tbl->mem_used -= (uint32_t)elem_bytes;
@@ -306,8 +306,8 @@ grpc_error *grpc_chttp2_hptbl_set_current_table_size(grpc_exec_ctx *exec_ctx,
 grpc_error *grpc_chttp2_hptbl_add(grpc_exec_ctx *exec_ctx,
                                   grpc_chttp2_hptbl *tbl, grpc_mdelem md) {
   /* determine how many bytes of buffer this entry represents */
-  size_t elem_bytes = GRPC_SLICE_LENGTH(md->key) +
-                      GRPC_SLICE_LENGTH(md->value) +
+  size_t elem_bytes = GRPC_SLICE_LENGTH(GRPC_MDKEY(md)) +
+                      GRPC_SLICE_LENGTH(GRPC_MDVALUE(md)) +
                       GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD;
 
   if (tbl->current_table_bytes > tbl->max_bytes) {
@@ -362,9 +362,9 @@ grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find(
   /* See if the string is in the static table */
   for (i = 0; i < GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) {
     grpc_mdelem ent = tbl->static_ents[i];
-    if (grpc_slice_cmp(md->key, ent->key) != 0) continue;
+    if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDKEY(ent)) != 0) continue;
     r.index = i + 1u;
-    r.has_value = grpc_slice_cmp(md->value, ent->value) == 0;
+    r.has_value = grpc_slice_cmp(GRPC_MDVALUE(md), GRPC_MDVALUE(ent)) == 0;
     if (r.has_value) return r;
   }
 
@@ -373,9 +373,9 @@ grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find(
     uint32_t idx =
         (uint32_t)(tbl->num_ents - i + GRPC_CHTTP2_LAST_STATIC_ENTRY);
     grpc_mdelem ent = tbl->ents[(tbl->first_ent + i) % tbl->cap_entries];
-    if (grpc_slice_cmp(md->key, ent->key) != 0) continue;
+    if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDKEY(ent)) != 0) continue;
     r.index = idx;
-    r.has_value = grpc_slice_cmp(md->value, ent->value) == 0;
+    r.has_value = grpc_slice_cmp(GRPC_MDVALUE(md), GRPC_MDVALUE(ent)) == 0;
     if (r.has_value) return r;
   }
 
diff --git a/src/core/ext/transport/chttp2/transport/parsing.c b/src/core/ext/transport/chttp2/transport/parsing.c
index 5cb460f3e231f86ccdec149d575e39e6fbc68d77..670ce51b725b1bdeb2879b4d2804be60a433a372 100644
--- a/src/core/ext/transport/chttp2/transport/parsing.c
+++ b/src/core/ext/transport/chttp2/transport/parsing.c
@@ -453,29 +453,30 @@ static void on_initial_header(grpc_exec_ctx *exec_ctx, void *tp,
   GPR_ASSERT(s != NULL);
 
   if (grpc_http_trace) {
-    char *key = grpc_dump_slice(md->key, GPR_DUMP_ASCII);
-    char *value = grpc_dump_slice(md->value, GPR_DUMP_HEX | GPR_DUMP_ASCII);
+    char *key = grpc_dump_slice(GRPC_MDKEY(md), GPR_DUMP_ASCII);
+    char *value =
+        grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
     gpr_log(GPR_INFO, "HTTP:%d:HDR:%s: %s: %s", s->id,
             t->is_client ? "CLI" : "SVR", key, value);
     gpr_free(key);
     gpr_free(value);
   }
 
-  if (grpc_slice_cmp(md->key, GRPC_MDSTR_GRPC_STATUS) == 0 &&
-      md != GRPC_MDELEM_GRPC_STATUS_0) {
+  if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_GRPC_STATUS) == 0 &&
+      !grpc_mdelem_eq(md, GRPC_MDELEM_GRPC_STATUS_0)) {
     /* TODO(ctiller): check for a status like " 0" */
     s->seen_error = true;
   }
 
-  if (grpc_slice_cmp(md->key, GRPC_MDSTR_GRPC_TIMEOUT) == 0) {
+  if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_GRPC_TIMEOUT) == 0) {
     gpr_timespec *cached_timeout = grpc_mdelem_get_user_data(md, free_timeout);
     if (!cached_timeout) {
       /* not already parsed: parse it now, and store the result away */
       cached_timeout = gpr_malloc(sizeof(gpr_timespec));
-      if (!grpc_http2_decode_timeout(GRPC_SLICE_START_PTR(md->value),
-                                     GRPC_SLICE_LENGTH(md->value),
+      if (!grpc_http2_decode_timeout(GRPC_SLICE_START_PTR(GRPC_MDVALUE(md)),
+                                     GRPC_SLICE_LENGTH(GRPC_MDVALUE(md)),
                                      cached_timeout)) {
-        char *val = grpc_dump_slice(md->value, GPR_DUMP_ASCII);
+        char *val = grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_ASCII);
         gpr_log(GPR_ERROR, "Ignoring bad timeout value '%s'", val);
         gpr_free(val);
         *cached_timeout = gpr_inf_future(GPR_TIMESPAN);
@@ -523,16 +524,17 @@ static void on_trailing_header(grpc_exec_ctx *exec_ctx, void *tp,
   GPR_ASSERT(s != NULL);
 
   if (grpc_http_trace) {
-    char *key = grpc_dump_slice(md->key, GPR_DUMP_ASCII);
-    char *value = grpc_dump_slice(md->value, GPR_DUMP_HEX | GPR_DUMP_ASCII);
+    char *key = grpc_dump_slice(GRPC_MDKEY(md), GPR_DUMP_ASCII);
+    char *value =
+        grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
     gpr_log(GPR_INFO, "HTTP:%d:TRL:%s: %s: %s", s->id,
             t->is_client ? "CLI" : "SVR", key, value);
     gpr_free(key);
     gpr_free(value);
   }
 
-  if (grpc_slice_cmp(md->key, GRPC_MDSTR_GRPC_STATUS) == 0 &&
-      md != GRPC_MDELEM_GRPC_STATUS_0) {
+  if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_GRPC_STATUS) == 0 &&
+      !grpc_mdelem_eq(md, GRPC_MDELEM_GRPC_STATUS_0)) {
     /* TODO(ctiller): check for a status like " 0" */
     s->seen_error = true;
   }
diff --git a/src/core/lib/channel/message_size_filter.c b/src/core/lib/channel/message_size_filter.c
index 4f61da825e3bc7a3bdf8c1811310f002a8217203..bddfba47700f083c3636a06803e2ea6f6dfc5a41 100644
--- a/src/core/lib/channel/message_size_filter.c
+++ b/src/core/lib/channel/message_size_filter.c
@@ -39,7 +39,8 @@
 #include <grpc/support/string_util.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/transport/method_config.h"
+#include "src/core/lib/support/string.h"
+#include "src/core/lib/transport/service_config.h"
 
 #define DEFAULT_MAX_SEND_MESSAGE_LENGTH -1  // Unlimited.
 // The protobuf library will (by default) start warning at 100 megs.
@@ -56,32 +57,33 @@ static void* message_size_limits_copy(void* value) {
   return new_value;
 }
 
-static int message_size_limits_cmp(void* value1, void* value2) {
-  const message_size_limits* v1 = value1;
-  const message_size_limits* v2 = value2;
-  if (v1->max_send_size > v2->max_send_size) return 1;
-  if (v1->max_send_size < v2->max_send_size) return -1;
-  if (v1->max_recv_size > v2->max_recv_size) return 1;
-  if (v1->max_recv_size < v2->max_recv_size) return -1;
-  return 0;
+static void message_size_limits_free(grpc_exec_ctx* exec_ctx, void* value) {
+  gpr_free(value);
 }
 
-static void free_mem(grpc_exec_ctx* exec_ctx, void* p) { gpr_free(p); }
-
 static const grpc_slice_hash_table_vtable message_size_limits_vtable = {
-    free_mem, message_size_limits_copy, message_size_limits_cmp};
+    message_size_limits_free, message_size_limits_copy};
 
-static void* method_config_convert_value(
-    const grpc_method_config* method_config) {
+static void* message_size_limits_create_from_json(const grpc_json* json) {
+  int max_request_message_bytes = -1;
+  int max_response_message_bytes = -1;
+  for (grpc_json* field = json->child; field != NULL; field = field->next) {
+    if (field->key == NULL) continue;
+    if (strcmp(field->key, "maxRequestMessageBytes") == 0) {
+      if (max_request_message_bytes >= 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      max_request_message_bytes = gpr_parse_nonnegative_int(field->value);
+      if (max_request_message_bytes == -1) return NULL;
+    } else if (strcmp(field->key, "maxResponseMessageBytes") == 0) {
+      if (max_response_message_bytes >= 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      max_response_message_bytes = gpr_parse_nonnegative_int(field->value);
+      if (max_response_message_bytes == -1) return NULL;
+    }
+  }
   message_size_limits* value = gpr_malloc(sizeof(message_size_limits));
-  const int32_t* max_request_message_bytes =
-      grpc_method_config_get_max_request_message_bytes(method_config);
-  value->max_send_size =
-      max_request_message_bytes != NULL ? *max_request_message_bytes : -1;
-  const int32_t* max_response_message_bytes =
-      grpc_method_config_get_max_response_message_bytes(method_config);
-  value->max_recv_size =
-      max_response_message_bytes != NULL ? *max_response_message_bytes : -1;
+  value->max_send_size = max_request_message_bytes;
+  value->max_recv_size = max_response_message_bytes;
   return value;
 }
 
@@ -225,10 +227,16 @@ static void init_channel_elem(grpc_exec_ctx* exec_ctx,
   const grpc_arg* channel_arg =
       grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG);
   if (channel_arg != NULL) {
-    GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
-    chand->method_limit_table = grpc_method_config_table_convert(
-        exec_ctx, (grpc_method_config_table*)channel_arg->value.pointer.p,
-        method_config_convert_value, &message_size_limits_vtable);
+    GPR_ASSERT(channel_arg->type == GRPC_ARG_STRING);
+    grpc_service_config* service_config =
+        grpc_service_config_create(channel_arg->value.string);
+    if (service_config != NULL) {
+      chand->method_limit_table =
+          grpc_service_config_create_method_config_table(
+              exec_ctx, service_config, message_size_limits_create_from_json,
+              &message_size_limits_vtable);
+      grpc_service_config_destroy(service_config);
+    }
   }
 }
 
diff --git a/src/core/lib/json/json.c b/src/core/lib/json/json.c
index 5b583a1f2e0f235294d200e2c37fb50ca389cba0..48b13686d7b24fe0f183f9391ad33e64cb3e2c0c 100644
--- a/src/core/lib/json/json.c
+++ b/src/core/lib/json/json.c
@@ -37,15 +37,15 @@
 
 #include "src/core/lib/json/json.h"
 
-grpc_json *grpc_json_create(grpc_json_type type) {
-  grpc_json *json = gpr_malloc(sizeof(*json));
+grpc_json* grpc_json_create(grpc_json_type type) {
+  grpc_json* json = gpr_malloc(sizeof(*json));
   memset(json, 0, sizeof(*json));
   json->type = type;
 
   return json;
 }
 
-void grpc_json_destroy(grpc_json *json) {
+void grpc_json_destroy(grpc_json* json) {
   while (json->child) {
     grpc_json_destroy(json->child);
   }
diff --git a/src/core/lib/json/json.h b/src/core/lib/json/json.h
index 681df4bb77da579245b8a2e47eccef2a2dfadb61..7111db0b52bd813fd1e344366b2f1355adf1be94 100644
--- a/src/core/lib/json/json.h
+++ b/src/core/lib/json/json.h
@@ -42,14 +42,14 @@
  * are not owned by it.
  */
 typedef struct grpc_json {
-  struct grpc_json *next;
-  struct grpc_json *prev;
-  struct grpc_json *child;
-  struct grpc_json *parent;
+  struct grpc_json* next;
+  struct grpc_json* prev;
+  struct grpc_json* child;
+  struct grpc_json* parent;
 
   grpc_json_type type;
-  const char *key;
-  const char *value;
+  const char* key;
+  const char* value;
 } grpc_json;
 
 /* The next two functions are going to parse the input string, and
@@ -65,8 +65,8 @@ typedef struct grpc_json {
  *
  * Delete the allocated tree afterward using grpc_json_destroy().
  */
-grpc_json *grpc_json_parse_string_with_len(char *input, size_t size);
-grpc_json *grpc_json_parse_string(char *input);
+grpc_json* grpc_json_parse_string_with_len(char* input, size_t size);
+grpc_json* grpc_json_parse_string(char* input);
 
 /* This function will create a new string using gpr_realloc, and will
  * deserialize the grpc_json tree into it. It'll be zero-terminated,
@@ -76,13 +76,13 @@ grpc_json *grpc_json_parse_string(char *input);
  * If indent is 0, then newlines will be suppressed as well, and the
  * output will be condensed at its maximum.
  */
-char *grpc_json_dump_to_string(grpc_json *json, int indent);
+char* grpc_json_dump_to_string(grpc_json* json, int indent);
 
 /* Use these to create or delete a grpc_json object.
  * Deletion is recursive. We will not attempt to free any of the strings
  * in any of the objects of that tree.
  */
-grpc_json *grpc_json_create(grpc_json_type type);
-void grpc_json_destroy(grpc_json *json);
+grpc_json* grpc_json_create(grpc_json_type type);
+void grpc_json_destroy(grpc_json* json);
 
 #endif /* GRPC_CORE_LIB_JSON_JSON_H */
diff --git a/src/core/lib/security/transport/client_auth_filter.c b/src/core/lib/security/transport/client_auth_filter.c
index e2e4a09e6216c9a33d1aae32fffce7805c95449e..d680c1ef13308599e2626a731ae7391666c335c9 100644
--- a/src/core/lib/security/transport/client_auth_filter.c
+++ b/src/core/lib/security/transport/client_auth_filter.c
@@ -256,17 +256,17 @@ static void auth_start_transport_op(grpc_exec_ctx *exec_ctx,
       grpc_mdelem md = l->md;
       /* Pointer comparison is OK for md_elems created from the same context.
        */
-      if (grpc_slice_cmp(md->key, GRPC_MDSTR_AUTHORITY) == 0) {
+      if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_AUTHORITY) == 0) {
         if (calld->have_host) {
           grpc_slice_unref_internal(exec_ctx, calld->host);
         }
-        calld->host = grpc_slice_ref_internal(md->value);
+        calld->host = grpc_slice_ref_internal(GRPC_MDVALUE(md));
         calld->have_host = true;
-      } else if (grpc_slice_cmp(md->key, GRPC_MDSTR_PATH) == 0) {
+      } else if (grpc_slice_cmp(GRPC_MDKEY(md), GRPC_MDSTR_PATH) == 0) {
         if (calld->have_method) {
           grpc_slice_unref_internal(exec_ctx, calld->method);
         }
-        calld->method = grpc_slice_ref_internal(md->value);
+        calld->method = grpc_slice_ref_internal(GRPC_MDVALUE(md));
         calld->have_method = true;
       }
     }
diff --git a/src/core/lib/security/transport/server_auth_filter.c b/src/core/lib/security/transport/server_auth_filter.c
index e7c0f1a079dd81444387d503539ea7fc8f9573da..1c4843fc1446bb4dcf1107688a7173d2300e0276 100644
--- a/src/core/lib/security/transport/server_auth_filter.c
+++ b/src/core/lib/security/transport/server_auth_filter.c
@@ -69,8 +69,8 @@ static grpc_metadata_array metadata_batch_to_md_array(
   for (l = batch->list.head; l != NULL; l = l->next) {
     grpc_metadata *usr_md = NULL;
     grpc_mdelem md = l->md;
-    grpc_slice key = md->key;
-    grpc_slice value = md->value;
+    grpc_slice key = GRPC_MDKEY(md);
+    grpc_slice value = GRPC_MDVALUE(md);
     if (result.count == result.capacity) {
       result.capacity = GPR_MAX(result.capacity + 8, result.capacity * 2);
       result.metadata =
@@ -90,9 +90,9 @@ static grpc_mdelem remove_consumed_md(grpc_exec_ctx *exec_ctx, void *user_data,
   size_t i;
   for (i = 0; i < calld->num_consumed_md; i++) {
     const grpc_metadata *consumed_md = &calld->consumed_md[i];
-    if (grpc_slice_cmp(md->key, consumed_md->key) == 0 &&
-        grpc_slice_cmp(md->value, consumed_md->value) == 0)
-      return NULL;
+    if (grpc_slice_cmp(GRPC_MDKEY(md), consumed_md->key) == 0 &&
+        grpc_slice_cmp(GRPC_MDKEY(md), consumed_md->value) == 0)
+      return GRPC_MDNULL;
   }
   return md;
 }
diff --git a/src/core/lib/slice/slice_hash_table.c b/src/core/lib/slice/slice_hash_table.c
index 10b41343ea2f04ed0c683d67b77c3076b7ea997c..55f20155f369833ca6b10f354e4b75b133321276 100644
--- a/src/core/lib/slice/slice_hash_table.c
+++ b/src/core/lib/slice/slice_hash_table.c
@@ -46,7 +46,6 @@ static const grpc_slice terminal_slice = {&terminal_slice_refcount,
 
 struct grpc_slice_hash_table {
   gpr_refcount refs;
-  size_t num_entries;
   size_t size;
   grpc_slice_hash_table_entry* entries;
 };
@@ -90,7 +89,6 @@ grpc_slice_hash_table* grpc_slice_hash_table_create(
   grpc_slice_hash_table* table = gpr_malloc(sizeof(*table));
   memset(table, 0, sizeof(*table));
   gpr_ref_init(&table->refs, 1);
-  table->num_entries = num_entries;
   // Quadratic probing gets best performance when the table is no more
   // than half full.
   table->size = num_entries * 2;
@@ -112,8 +110,8 @@ grpc_slice_hash_table* grpc_slice_hash_table_ref(grpc_slice_hash_table* table) {
   return table;
 }
 
-int grpc_slice_hash_table_unref(grpc_exec_ctx* exec_ctx,
-                                grpc_slice_hash_table* table) {
+void grpc_slice_hash_table_unref(grpc_exec_ctx* exec_ctx,
+                                 grpc_slice_hash_table* table) {
   if (table != NULL && gpr_unref(&table->refs)) {
     for (size_t i = 0; i < table->size; ++i) {
       grpc_slice_hash_table_entry* entry = &table->entries[i];
@@ -124,13 +122,7 @@ int grpc_slice_hash_table_unref(grpc_exec_ctx* exec_ctx,
     }
     gpr_free(table->entries);
     gpr_free(table);
-    return 1;
   }
-  return 0;
-}
-
-size_t grpc_slice_hash_table_num_entries(const grpc_slice_hash_table* table) {
-  return table->num_entries;
 }
 
 void* grpc_slice_hash_table_get(const grpc_slice_hash_table* table,
@@ -140,35 +132,3 @@ void* grpc_slice_hash_table_get(const grpc_slice_hash_table* table,
   if (idx == table->size) return NULL;  // Not found.
   return table->entries[idx].value;
 }
-
-int grpc_slice_hash_table_cmp(const grpc_slice_hash_table* table1,
-                              const grpc_slice_hash_table* table2) {
-  // Compare by num_entries.
-  if (table1->num_entries < table2->num_entries) return -1;
-  if (table1->num_entries > table2->num_entries) return 1;
-  for (size_t i = 0; i < table1->num_entries; ++i) {
-    grpc_slice_hash_table_entry* e1 = &table1->entries[i];
-    grpc_slice_hash_table_entry* e2 = &table2->entries[i];
-    // Compare keys by hash value.
-    int cmp = grpc_slice_cmp(e1->key, e2->key);
-    if (cmp != 0) return cmp;
-    // Compare by vtable (pointer equality).
-    if (e1->vtable < e2->vtable) return -1;
-    if (e1->vtable > e2->vtable) return 1;
-    // Compare values via vtable.
-    const int value_result = e1->vtable->compare_value(e1->value, e2->value);
-    if (value_result != 0) return value_result;
-  }
-  return 0;
-}
-
-void grpc_slice_hash_table_iterate(
-    const grpc_slice_hash_table* table,
-    void (*func)(const grpc_slice_hash_table_entry* entry, void* user_data),
-    void* user_data) {
-  for (size_t i = 0; i < table->size; ++i) {
-    if (!is_terminal(table->entries[i].key)) {
-      func(&table->entries[i], user_data);
-    }
-  }
-}
diff --git a/src/core/lib/slice/slice_hash_table.h b/src/core/lib/slice/slice_hash_table.h
index ac04950cc664b22a0dba2c3b0ba5006e0749c2f4..f760836a489792152c0fe669606780a56082c1d9 100644
--- a/src/core/lib/slice/slice_hash_table.h
+++ b/src/core/lib/slice/slice_hash_table.h
@@ -51,7 +51,6 @@ typedef struct grpc_slice_hash_table grpc_slice_hash_table;
 typedef struct grpc_slice_hash_table_vtable {
   void (*destroy_value)(grpc_exec_ctx *exec_ctx, void *value);
   void *(*copy_value)(void *value);
-  int (*compare_value)(void *value1, void *value2);
 } grpc_slice_hash_table_vtable;
 
 typedef struct grpc_slice_hash_table_entry {
@@ -68,26 +67,12 @@ grpc_slice_hash_table *grpc_slice_hash_table_create(
 
 grpc_slice_hash_table *grpc_slice_hash_table_ref(grpc_slice_hash_table *table);
 /** Returns 1 when \a table is destroyed. */
-int grpc_slice_hash_table_unref(grpc_exec_ctx *exec_ctx,
-                                grpc_slice_hash_table *table);
-
-/** Returns the number of entries in \a table. */
-size_t grpc_slice_hash_table_num_entries(const grpc_slice_hash_table *table);
+void grpc_slice_hash_table_unref(grpc_exec_ctx *exec_ctx,
+                                 grpc_slice_hash_table *table);
 
 /** Returns the value from \a table associated with \a key.
     Returns NULL if \a key is not found. */
 void *grpc_slice_hash_table_get(const grpc_slice_hash_table *table,
                                 const grpc_slice key);
 
-/** Compares two hash tables.
-    The sort order is stable but undefined. */
-int grpc_slice_hash_table_cmp(const grpc_slice_hash_table *table1,
-                              const grpc_slice_hash_table *table2);
-
-/** Iterates over the entries in \a table, calling \a func for each entry. */
-void grpc_slice_hash_table_iterate(
-    const grpc_slice_hash_table *table,
-    void (*func)(const grpc_slice_hash_table_entry *entry, void *user_data),
-    void *user_data);
-
 #endif /* GRPC_CORE_LIB_TRANSPORT_MDSTR_HASH_TABLE_H */
diff --git a/src/core/lib/support/string.c b/src/core/lib/support/string.c
index 85b915f118fa33dc9d1e0aaf99407c20510b6c40..0dc55c2ca34a2433baf8d5c95c1e57d106ca761c 100644
--- a/src/core/lib/support/string.c
+++ b/src/core/lib/support/string.c
@@ -34,7 +34,9 @@
 #include "src/core/lib/support/string.h"
 
 #include <ctype.h>
+#include <limits.h>
 #include <stddef.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <grpc/support/alloc.h>
@@ -189,6 +191,13 @@ int int64_ttoa(int64_t value, char *string) {
   return i;
 }
 
+int gpr_parse_nonnegative_int(const char *value) {
+  char *end;
+  long result = strtol(value, &end, 0);
+  if (*end != '\0' || result < 0 || result > INT_MAX) return -1;
+  return (int)result;
+}
+
 char *gpr_leftpad(const char *str, char flag, size_t length) {
   const size_t str_length = strlen(str);
   const size_t out_length = str_length > length ? str_length : length;
diff --git a/src/core/lib/support/string.h b/src/core/lib/support/string.h
index 81a12ae476ee758a99862e52dcbb68dcfcdacc37..43ab4dc1bec36fd9447489814b464392f2226ce8 100644
--- a/src/core/lib/support/string.h
+++ b/src/core/lib/support/string.h
@@ -77,6 +77,9 @@ NOTE: This function ensures sufficient bit width even on Win x64,
 where long is 32bit is size.*/
 int int64_ttoa(int64_t value, char *output);
 
+// Parses a non-negative number from a value string.  Returns -1 on error.
+int gpr_parse_nonnegative_int(const char *value);
+
 /* Reverse a run of bytes */
 void gpr_reverse_bytes(char *str, int len);
 
diff --git a/src/core/lib/transport/service_config.c b/src/core/lib/transport/service_config.c
new file mode 100644
index 0000000000000000000000000000000000000000..8073a46105fdc824c9bbc1cd83bae618fbd31e51
--- /dev/null
+++ b/src/core/lib/transport/service_config.c
@@ -0,0 +1,254 @@
+//
+// Copyright 2015, 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 "src/core/lib/transport/service_config.h"
+
+#include <string.h>
+
+#include <grpc/impl/codegen/grpc_types.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/json/json.h"
+#include "src/core/lib/slice/slice_hash_table.h"
+#include "src/core/lib/slice/slice_internal.h"
+#include "src/core/lib/slice/slice_string_helpers.h"
+#include "src/core/lib/support/string.h"
+
+// The main purpose of the code here is to parse the service config in
+// JSON form, which will look like this:
+//
+// {
+//   "loadBalancingPolicy": "string",  // optional
+//   "methodConfig": [  // array of one or more method_config objects
+//     {
+//       "name": [  // array of one or more name objects
+//         {
+//           "service": "string",  // required
+//           "method": "string",  // optional
+//         }
+//       ],
+//       // remaining fields are optional.
+//       // see https://developers.google.com/protocol-buffers/docs/proto3#json
+//       // for format details.
+//       "waitForReady": bool,
+//       "timeout": "duration_string",
+//       "maxRequestMessageBytes": "int64_string",
+//       "maxResponseMessageBytes": "int64_string",
+//     }
+//   ]
+// }
+
+struct grpc_service_config {
+  char* json_string;  // Underlying storage for json_tree.
+  grpc_json* json_tree;
+};
+
+grpc_service_config* grpc_service_config_create(const char* json_string) {
+  grpc_service_config* service_config = gpr_malloc(sizeof(*service_config));
+  service_config->json_string = gpr_strdup(json_string);
+  service_config->json_tree =
+      grpc_json_parse_string(service_config->json_string);
+  if (service_config->json_tree == NULL) {
+    gpr_log(GPR_INFO, "failed to parse JSON for service config");
+    gpr_free(service_config->json_string);
+    gpr_free(service_config);
+    return NULL;
+  }
+  return service_config;
+}
+
+void grpc_service_config_destroy(grpc_service_config* service_config) {
+  grpc_json_destroy(service_config->json_tree);
+  gpr_free(service_config->json_string);
+  gpr_free(service_config);
+}
+
+const char* grpc_service_config_get_lb_policy_name(
+    const grpc_service_config* service_config) {
+  const grpc_json* json = service_config->json_tree;
+  if (json->type != GRPC_JSON_OBJECT || json->key != NULL) return NULL;
+  const char* lb_policy_name = NULL;
+  for (grpc_json* field = json->child; field != NULL; field = field->next) {
+    if (field->key == NULL) return NULL;
+    if (strcmp(field->key, "loadBalancingPolicy") == 0) {
+      if (lb_policy_name != NULL) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      lb_policy_name = field->value;
+    }
+  }
+  return lb_policy_name;
+}
+
+// Returns the number of names specified in the method config \a json.
+static size_t count_names_in_method_config_json(grpc_json* json) {
+  size_t num_names = 0;
+  for (grpc_json* field = json->child; field != NULL; field = field->next) {
+    if (field->key != NULL && strcmp(field->key, "name") == 0) ++num_names;
+  }
+  return num_names;
+}
+
+// Returns a path string for the JSON name object specified by \a json.
+// Returns NULL on error.  Caller takes ownership of result.
+static char* parse_json_method_name(grpc_json* json) {
+  if (json->type != GRPC_JSON_OBJECT) return NULL;
+  const char* service_name = NULL;
+  const char* method_name = NULL;
+  for (grpc_json* child = json->child; child != NULL; child = child->next) {
+    if (child->key == NULL) return NULL;
+    if (child->type != GRPC_JSON_STRING) return NULL;
+    if (strcmp(child->key, "service") == 0) {
+      if (service_name != NULL) return NULL;  // Duplicate.
+      if (child->value == NULL) return NULL;
+      service_name = child->value;
+    } else if (strcmp(child->key, "method") == 0) {
+      if (method_name != NULL) return NULL;  // Duplicate.
+      if (child->value == NULL) return NULL;
+      method_name = child->value;
+    }
+  }
+  if (service_name == NULL) return NULL;  // Required field.
+  char* path;
+  gpr_asprintf(&path, "/%s/%s", service_name,
+               method_name == NULL ? "*" : method_name);
+  return path;
+}
+
+// Parses the method config from \a json.  Adds an entry to \a entries for
+// each name found, incrementing \a idx for each entry added.
+// Returns false on error.
+static bool parse_json_method_config(
+    grpc_exec_ctx* exec_ctx, grpc_json* json,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_slice_hash_table_vtable* vtable,
+    grpc_slice_hash_table_entry* entries, size_t* idx) {
+  // Construct value.
+  void* method_config = create_value(json);
+  if (method_config == NULL) return false;
+  // Construct list of paths.
+  bool success = false;
+  gpr_strvec paths;
+  gpr_strvec_init(&paths);
+  for (grpc_json* child = json->child; child != NULL; child = child->next) {
+    if (child->key == NULL) continue;
+    if (strcmp(child->key, "name") == 0) {
+      if (child->type != GRPC_JSON_ARRAY) goto done;
+      for (grpc_json* name = child->child; name != NULL; name = name->next) {
+        char* path = parse_json_method_name(name);
+        gpr_strvec_add(&paths, path);
+      }
+    }
+  }
+  if (paths.count == 0) goto done;  // No names specified.
+  // Add entry for each path.
+  for (size_t i = 0; i < paths.count; ++i) {
+    entries[*idx].key = grpc_slice_from_copied_string(paths.strs[i]);
+    entries[*idx].value = vtable->copy_value(method_config);
+    entries[*idx].vtable = vtable;
+    ++*idx;
+  }
+  success = true;
+done:
+  vtable->destroy_value(exec_ctx, method_config);
+  gpr_strvec_destroy(&paths);
+  return success;
+}
+
+grpc_slice_hash_table* grpc_service_config_create_method_config_table(
+    grpc_exec_ctx* exec_ctx, const grpc_service_config* service_config,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_slice_hash_table_vtable* vtable) {
+  const grpc_json* json = service_config->json_tree;
+  // Traverse parsed JSON tree.
+  if (json->type != GRPC_JSON_OBJECT || json->key != NULL) return NULL;
+  size_t num_entries = 0;
+  grpc_slice_hash_table_entry* entries = NULL;
+  for (grpc_json* field = json->child; field != NULL; field = field->next) {
+    if (field->key == NULL) return NULL;
+    if (strcmp(field->key, "methodConfig") == 0) {
+      if (entries != NULL) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_ARRAY) return NULL;
+      // Find number of entries.
+      for (grpc_json* method = field->child; method != NULL;
+           method = method->next) {
+        num_entries += count_names_in_method_config_json(method);
+      }
+      // Populate method config table entries.
+      entries = gpr_malloc(num_entries * sizeof(grpc_slice_hash_table_entry));
+      size_t idx = 0;
+      for (grpc_json* method = field->child; method != NULL;
+           method = method->next) {
+        if (!parse_json_method_config(exec_ctx, method, create_value, vtable,
+                                      entries, &idx)) {
+          return NULL;
+        }
+      }
+      GPR_ASSERT(idx == num_entries);
+    }
+  }
+  // Instantiate method config table.
+  grpc_slice_hash_table* method_config_table = NULL;
+  if (entries != NULL) {
+    method_config_table = grpc_slice_hash_table_create(num_entries, entries);
+    // Clean up.
+    for (size_t i = 0; i < num_entries; ++i) {
+      grpc_slice_unref_internal(exec_ctx, entries[i].key);
+      vtable->destroy_value(exec_ctx, entries[i].value);
+    }
+    gpr_free(entries);
+  }
+  return method_config_table;
+}
+
+void* grpc_method_config_table_get(grpc_exec_ctx* exec_ctx,
+                                   const grpc_slice_hash_table* table,
+                                   grpc_slice path) {
+  void* value = grpc_slice_hash_table_get(table, path);
+  // If we didn't find a match for the path, try looking for a wildcard
+  // entry (i.e., change "/service/method" to "/service/*").
+  if (value == NULL) {
+    char* path_str = grpc_dump_slice(path, GPR_DUMP_ASCII);
+    const char* sep = strrchr(path_str, '/') + 1;
+    const size_t len = (size_t)(sep - path_str);
+    char* buf = gpr_malloc(len + 2);  // '*' and NUL
+    memcpy(buf, path_str, len);
+    buf[len] = '*';
+    buf[len + 1] = '\0';
+    grpc_slice wildcard_path = grpc_slice_from_copied_string(buf);
+    gpr_free(buf);
+    value = grpc_slice_hash_table_get(table, wildcard_path);
+    grpc_slice_unref_internal(exec_ctx, wildcard_path);
+    gpr_free(path_str);
+  }
+  return value;
+}
diff --git a/src/core/lib/transport/service_config.h b/src/core/lib/transport/service_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab964b3bae3809975d1c71fdfbea36a35992e8bb
--- /dev/null
+++ b/src/core/lib/transport/service_config.h
@@ -0,0 +1,71 @@
+//
+// 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_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H
+#define GRPC_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H
+
+#include <grpc/impl/codegen/grpc_types.h>
+
+#include "src/core/lib/json/json.h"
+#include "src/core/lib/slice/slice_hash_table.h"
+
+typedef struct grpc_service_config grpc_service_config;
+
+grpc_service_config* grpc_service_config_create(const char* json_string);
+void grpc_service_config_destroy(grpc_service_config* service_config);
+
+/// Gets the LB policy name from \a service_config.
+/// Returns NULL if no LB policy name was specified.
+/// Caller does NOT take ownership.
+const char* grpc_service_config_get_lb_policy_name(
+    const grpc_service_config* service_config);
+
+/// Creates a method config table based on the data in \a json.
+/// The table's keys are request paths.  The table's value type is
+/// returned by \a create_value(), based on data parsed from the JSON tree.
+/// \a vtable provides methods used to manage the values.
+/// Returns NULL on error.
+grpc_slice_hash_table* grpc_service_config_create_method_config_table(
+    grpc_exec_ctx* exec_ctx, const grpc_service_config* service_config,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_slice_hash_table_vtable* vtable);
+
+/// A helper function for looking up values in the table returned by
+/// \a grpc_service_config_create_method_config_table().
+/// Gets the method config for the specified \a path, which should be of
+/// the form "/service/method".
+/// Returns NULL if the method has no config.
+/// Caller does NOT own a reference to the result.
+void* grpc_method_config_table_get(grpc_exec_ctx* exec_ctx,
+                                   const grpc_slice_hash_table* table,
+                                   const grpc_slice path);
+
+#endif /* GRPC_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H */
diff --git a/src/core/lib/transport/transport_op_string.c b/src/core/lib/transport/transport_op_string.c
index 0259fa23eb34e7f87df82c65207ade184153f045..b36e4f22d0f3860e8725c7de7c389864718dd121 100644
--- a/src/core/lib/transport/transport_op_string.c
+++ b/src/core/lib/transport/transport_op_string.c
@@ -49,10 +49,12 @@
 
 static void put_metadata(gpr_strvec *b, grpc_mdelem md) {
   gpr_strvec_add(b, gpr_strdup("key="));
-  gpr_strvec_add(b, grpc_dump_slice(md->key, GPR_DUMP_HEX | GPR_DUMP_ASCII));
+  gpr_strvec_add(
+      b, grpc_dump_slice(GRPC_MDKEY(md), GPR_DUMP_HEX | GPR_DUMP_ASCII));
 
   gpr_strvec_add(b, gpr_strdup(" value="));
-  gpr_strvec_add(b, grpc_dump_slice(md->value, GPR_DUMP_HEX | GPR_DUMP_ASCII));
+  gpr_strvec_add(
+      b, grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII));
 }
 
 static void put_metadata_list(gpr_strvec *b, grpc_metadata_batch md) {
diff --git a/src/cpp/client/channel_cc.cc b/src/cpp/client/channel_cc.cc
index 5f1d00d2b49708fc2161ccaa85198c89e8d045b8..357d8317adfc8eed1744d7e9221396f918778c9f 100644
--- a/src/cpp/client/channel_cc.cc
+++ b/src/cpp/client/channel_cc.cc
@@ -48,6 +48,7 @@
 #include <grpc++/support/time.h>
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include "src/core/lib/profiling/timers.h"
 
@@ -61,6 +62,35 @@ Channel::Channel(const grpc::string& host, grpc_channel* channel)
 
 Channel::~Channel() { grpc_channel_destroy(c_channel_); }
 
+namespace {
+
+grpc::string GetChannelInfoField(grpc_channel* channel,
+                                 grpc_channel_info* channel_info,
+                                 char*** channel_info_field) {
+  char* value = NULL;
+  memset(channel_info, 0, sizeof(*channel_info));
+  *channel_info_field = &value;
+  grpc_channel_get_info(channel, channel_info);
+  if (value == NULL) return "";
+  grpc::string result = value;
+  gpr_free(value);
+  return result;
+}
+
+}  // namespace
+
+grpc::string Channel::GetLoadBalancingPolicyName() const {
+  grpc_channel_info channel_info;
+  return GetChannelInfoField(c_channel_, &channel_info,
+                             &channel_info.lb_policy_name);
+}
+
+grpc::string Channel::GetServiceConfigJSON() const {
+  grpc_channel_info channel_info;
+  return GetChannelInfoField(c_channel_, &channel_info,
+                             &channel_info.service_config_json);
+}
+
 Call Channel::CreateCall(const RpcMethod& method, ClientContext* context,
                          CompletionQueue* cq) {
   const bool kRegistered = method.channel_tag() && context->authority().empty();
diff --git a/src/cpp/common/channel_arguments.cc b/src/cpp/common/channel_arguments.cc
index bc0b68b3e044945f6fa38055edeb65efd46bb395..1fdd10613033f1f9208421f05bddc2121df28151 100644
--- a/src/cpp/common/channel_arguments.cc
+++ b/src/cpp/common/channel_arguments.cc
@@ -148,6 +148,11 @@ void ChannelArguments::SetLoadBalancingPolicyName(
   SetString(GRPC_ARG_LB_POLICY_NAME, lb_policy_name);
 }
 
+void ChannelArguments::SetServiceConfigJSON(
+    const grpc::string& service_config_json) {
+  SetString(GRPC_ARG_SERVICE_CONFIG, service_config_json);
+}
+
 void ChannelArguments::SetInt(const grpc::string& key, int value) {
   grpc_arg arg;
   arg.type = GRPC_ARG_INTEGER;
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index e53ed72c1b6e55ca489de66bf86d536ab1dbb783..e25c14c2d365b7756332430204c83d708566700c 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -182,8 +182,8 @@ CORE_SOURCE_FILES = [
   'src/core/lib/transport/connectivity_state.c',
   'src/core/lib/transport/metadata.c',
   'src/core/lib/transport/metadata_batch.c',
-  'src/core/lib/transport/method_config.c',
   'src/core/lib/transport/pid_controller.c',
+  'src/core/lib/transport/service_config.c',
   'src/core/lib/transport/static_metadata.c',
   'src/core/lib/transport/timeout_encoding.c',
   'src/core/lib/transport/transport.c',
diff --git a/src/python/grpcio_tests/tests/interop/client.py b/src/python/grpcio_tests/tests/interop/client.py
index 4fbf58f7d93052ff6136fec1c7f276d06daf3999..afaa4662541d7ba07653ba05e72e19f9cb002270 100644
--- a/src/python/grpcio_tests/tests/interop/client.py
+++ b/src/python/grpcio_tests/tests/interop/client.py
@@ -43,11 +43,13 @@ from tests.interop import resources
 def _args():
   parser = argparse.ArgumentParser()
   parser.add_argument(
-      '--server_host', help='the host to which to connect', type=str)
+      '--server_host', help='the host to which to connect', type=str,
+      default="127.0.0.1")
   parser.add_argument(
       '--server_port', help='the port to which to connect', type=int)
   parser.add_argument(
-      '--test_case', help='the test case to execute', type=str)
+      '--test_case', help='the test case to execute', type=str,
+      default="large_unary")
   parser.add_argument(
       '--use_tls', help='require a secure connection', default=False,
       type=resources.parse_bool)
@@ -55,7 +57,7 @@ def _args():
       '--use_test_ca', help='replace platform root CAs with ca.pem',
       default=False, type=resources.parse_bool)
   parser.add_argument(
-      '--server_host_override',
+      '--server_host_override', default="foo.test.google.fr",
       help='the server host to which to claim to connect', type=str)
   parser.add_argument('--oauth_scope', help='scope for OAuth tokens', type=str)
   parser.add_argument(
diff --git a/test/core/client_channel/lb_policies_test.c b/test/core/client_channel/lb_policies_test.c
index 0a5c543a55b00887f7943aa0cee8f6d2b8a413ea..4b4c359d6f6666f75f6e918e62254bde9960f0c7 100644
--- a/test/core/client_channel/lb_policies_test.c
+++ b/test/core/client_channel/lb_policies_test.c
@@ -43,6 +43,7 @@
 
 #include "src/core/ext/client_channel/client_channel.h"
 #include "src/core/ext/client_channel/lb_policy_registry.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/support/string.h"
 #include "src/core/lib/surface/channel.h"
@@ -521,7 +522,7 @@ static grpc_channel *create_client(const servers_fixture *f) {
   arg_array[0].value.integer = RETRY_TIMEOUT;
   arg_array[1].type = GRPC_ARG_STRING;
   arg_array[1].key = GRPC_ARG_LB_POLICY_NAME;
-  arg_array[1].value.string = "round_robin";
+  arg_array[1].value.string = "ROUND_ROBIN";
   args.num_args = 2;
   args.args = arg_array;
 
@@ -610,29 +611,47 @@ static void test_pending_calls(size_t concurrent_calls) {
 }
 
 static void test_get_channel_info() {
-  grpc_channel_args args;
-  grpc_arg arg_array[1];
-  arg_array[0].type = GRPC_ARG_STRING;
-  arg_array[0].key = GRPC_ARG_LB_POLICY_NAME;
-  arg_array[0].value.string = "round_robin";
-  args.num_args = 1;
-  args.args = arg_array;
-
   grpc_channel *channel =
-      grpc_insecure_channel_create("ipv4:127.0.0.1:1234", &args, NULL);
+      grpc_insecure_channel_create("ipv4:127.0.0.1:1234", NULL, NULL);
   // Ensures that resolver returns.
   grpc_channel_check_connectivity_state(channel, true /* try_to_connect */);
-  // Use grpc_channel_get_info() to get LB policy name.
-  char *lb_policy_name = NULL;
+  // First, request no fields.  This is a no-op.
   grpc_channel_info channel_info;
+  memset(&channel_info, 0, sizeof(channel_info));
+  grpc_channel_get_info(channel, &channel_info);
+  // Request LB policy name.
+  char *lb_policy_name = NULL;
   channel_info.lb_policy_name = &lb_policy_name;
   grpc_channel_get_info(channel, &channel_info);
   GPR_ASSERT(lb_policy_name != NULL);
-  GPR_ASSERT(strcmp(lb_policy_name, "round_robin") == 0);
+  GPR_ASSERT(strcmp(lb_policy_name, "pick_first") == 0);
   gpr_free(lb_policy_name);
-  // Try again without requesting anything.  This is a no-op.
-  channel_info.lb_policy_name = NULL;
+  // Request service config, which does not exist, so we'll get nothing back.
+  memset(&channel_info, 0, sizeof(channel_info));
+  char *service_config_json = "dummy_string";
+  channel_info.service_config_json = &service_config_json;
+  grpc_channel_get_info(channel, &channel_info);
+  GPR_ASSERT(service_config_json == NULL);
+  // Recreate the channel such that it has a service config.
+  grpc_channel_destroy(channel);
+  grpc_arg arg;
+  arg.type = GRPC_ARG_STRING;
+  arg.key = GRPC_ARG_SERVICE_CONFIG;
+  arg.value.string = "{\"loadBalancingPolicy\": \"ROUND_ROBIN\"}";
+  grpc_channel_args *args = grpc_channel_args_copy_and_add(NULL, &arg, 1);
+  channel = grpc_insecure_channel_create("ipv4:127.0.0.1:1234", args, NULL);
+  {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+    grpc_channel_args_destroy(&exec_ctx, args);
+    grpc_exec_ctx_finish(&exec_ctx);
+  }
+  // Ensures that resolver returns.
+  grpc_channel_check_connectivity_state(channel, true /* try_to_connect */);
+  // Now request the service config again.
   grpc_channel_get_info(channel, &channel_info);
+  GPR_ASSERT(service_config_json != NULL);
+  GPR_ASSERT(strcmp(service_config_json, arg.value.string) == 0);
+  gpr_free(service_config_json);
   // Clean up.
   grpc_channel_destroy(channel);
 }
diff --git a/test/core/end2end/connection_refused_test.c b/test/core/end2end/connection_refused_test.c
index a74294892209731ef58059c38a25613899294efa..81a6b8720d47fe0a9400cb640123f1790452f7bd 100644
--- a/test/core/end2end/connection_refused_test.c
+++ b/test/core/end2end/connection_refused_test.c
@@ -42,7 +42,7 @@
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/transport/metadata.h"
-#include "src/core/lib/transport/method_config.h"
+#include "src/core/lib/transport/service_config.h"
 
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/util/port.h"
@@ -75,21 +75,20 @@ static void run_test(bool wait_for_ready, bool use_service_config) {
   /* if using service config, create channel args */
   grpc_channel_args *args = NULL;
   if (use_service_config) {
-    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
     GPR_ASSERT(wait_for_ready);
-    grpc_method_config_table_entry entry = {
-        grpc_slice_from_static_string("/service/method"),
-        grpc_method_config_create(&wait_for_ready, NULL, NULL, NULL),
-    };
-    grpc_method_config_table *method_config_table =
-        grpc_method_config_table_create(1, &entry);
-    grpc_slice_unref_internal(&exec_ctx, entry.method_name);
-    grpc_method_config_unref(&exec_ctx, entry.method_config);
-    grpc_arg arg =
-        grpc_method_config_table_create_channel_arg(method_config_table);
+    grpc_arg arg;
+    arg.type = GRPC_ARG_STRING;
+    arg.key = GRPC_ARG_SERVICE_CONFIG;
+    arg.value.string =
+        "{\n"
+        "  \"methodConfig\": [ {\n"
+        "    \"name\": [\n"
+        "      { \"service\": \"service\", \"method\": \"method\" }\n"
+        "    ],\n"
+        "    \"waitForReady\": true\n"
+        "  } ]\n"
+        "}";
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
-    grpc_method_config_table_unref(&exec_ctx, method_config_table);
-    grpc_exec_ctx_finish(&exec_ctx);
   }
 
   /* create a call, channel to a port which will refuse connection */
diff --git a/test/core/end2end/tests/cancel_after_accept.c b/test/core/end2end/tests/cancel_after_accept.c
index a2301d725cf4cab8479afcfabb8ffca11407195d..a0bec34ec9f51696cab0e6e9530d65bc39f4c339 100644
--- a/test/core/end2end/tests/cancel_after_accept.c
+++ b/test/core/end2end/tests/cancel_after_accept.c
@@ -45,7 +45,7 @@
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/transport/metadata.h"
-#include "src/core/lib/transport/method_config.h"
+#include "src/core/lib/transport/service_config.h"
 
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/tests/cancel_test_helpers.h"
@@ -134,21 +134,19 @@ static void test_cancel_after_accept(grpc_end2end_test_config config,
 
   grpc_channel_args *args = NULL;
   if (use_service_config) {
-    gpr_timespec timeout = {5, 0, GPR_TIMESPAN};
-    grpc_method_config_table_entry entry = {
-        grpc_slice_from_static_string("/service/method"),
-        grpc_method_config_create(NULL, &timeout, NULL, NULL),
-    };
-    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
-    grpc_method_config_table *method_config_table =
-        grpc_method_config_table_create(1, &entry);
-    grpc_slice_unref_internal(&exec_ctx, entry.method_name);
-    grpc_method_config_unref(&exec_ctx, entry.method_config);
-    grpc_arg arg =
-        grpc_method_config_table_create_channel_arg(method_config_table);
+    grpc_arg arg;
+    arg.type = GRPC_ARG_STRING;
+    arg.key = GRPC_ARG_SERVICE_CONFIG;
+    arg.value.string =
+        "{\n"
+        "  \"methodConfig\": [ {\n"
+        "    \"name\": [\n"
+        "      { \"service\": \"service\", \"method\": \"method\" }\n"
+        "    ],\n"
+        "    \"timeout\": \"5s\"\n"
+        "  } ]\n"
+        "}";
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
-    grpc_method_config_table_unref(&exec_ctx, method_config_table);
-    grpc_exec_ctx_finish(&exec_ctx);
   }
 
   grpc_end2end_test_fixture f =
diff --git a/test/core/end2end/tests/max_message_length.c b/test/core/end2end/tests/max_message_length.c
index 3fbb92863c814797578182504ba76149073f8614..4140df9aadec882f145b5269e518449f89a8cee5 100644
--- a/test/core/end2end/tests/max_message_length.c
+++ b/test/core/end2end/tests/max_message_length.c
@@ -45,7 +45,7 @@
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/transport/metadata.h"
-#include "src/core/lib/transport/method_config.h"
+#include "src/core/lib/transport/service_config.h"
 
 #include "test/core/end2end/cq_verifier.h"
 
@@ -137,22 +137,20 @@ static void test_max_message_length_on_request(grpc_end2end_test_config config,
   grpc_channel_args *server_args = NULL;
   if (use_service_config) {
     // We don't currently support service configs on the server side.
-    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
     GPR_ASSERT(send_limit);
-    int32_t max_request_message_bytes = 5;
-    grpc_method_config_table_entry entry = {
-        grpc_slice_from_static_string("/service/method"),
-        grpc_method_config_create(NULL, NULL, &max_request_message_bytes, NULL),
-    };
-    grpc_method_config_table *method_config_table =
-        grpc_method_config_table_create(1, &entry);
-    grpc_slice_unref_internal(&exec_ctx, entry.method_name);
-    grpc_method_config_unref(&exec_ctx, entry.method_config);
-    grpc_arg arg =
-        grpc_method_config_table_create_channel_arg(method_config_table);
+    grpc_arg arg;
+    arg.type = GRPC_ARG_STRING;
+    arg.key = GRPC_ARG_SERVICE_CONFIG;
+    arg.value.string =
+        "{\n"
+        "  \"methodConfig\": [ {\n"
+        "    \"name\": [\n"
+        "      { \"service\": \"service\", \"method\": \"method\" }\n"
+        "    ],\n"
+        "    \"maxRequestMessageBytes\": \"5\"\n"
+        "  } ]\n"
+        "}";
     client_args = grpc_channel_args_copy_and_add(NULL, &arg, 1);
-    grpc_method_config_table_unref(&exec_ctx, method_config_table);
-    grpc_exec_ctx_finish(&exec_ctx);
   } else {
     // Set limit via channel args.
     grpc_arg arg;
@@ -317,23 +315,20 @@ static void test_max_message_length_on_response(grpc_end2end_test_config config,
   grpc_channel_args *server_args = NULL;
   if (use_service_config) {
     // We don't currently support service configs on the server side.
-    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
     GPR_ASSERT(!send_limit);
-    int32_t max_response_message_bytes = 5;
-    grpc_method_config_table_entry entry = {
-        grpc_slice_from_static_string("/service/method"),
-        grpc_method_config_create(NULL, NULL, NULL,
-                                  &max_response_message_bytes),
-    };
-    grpc_method_config_table *method_config_table =
-        grpc_method_config_table_create(1, &entry);
-    grpc_slice_unref_internal(&exec_ctx, entry.method_name);
-    grpc_method_config_unref(&exec_ctx, entry.method_config);
-    grpc_arg arg =
-        grpc_method_config_table_create_channel_arg(method_config_table);
+    grpc_arg arg;
+    arg.type = GRPC_ARG_STRING;
+    arg.key = GRPC_ARG_SERVICE_CONFIG;
+    arg.value.string =
+        "{\n"
+        "  \"methodConfig\": [ {\n"
+        "    \"name\": [\n"
+        "      { \"service\": \"service\", \"method\": \"method\" }\n"
+        "    ],\n"
+        "    \"maxResponseMessageBytes\": \"5\"\n"
+        "  } ]\n"
+        "}";
     client_args = grpc_channel_args_copy_and_add(NULL, &arg, 1);
-    grpc_method_config_table_unref(&exec_ctx, method_config_table);
-    grpc_exec_ctx_finish(&exec_ctx);
   } else {
     // Set limit via channel args.
     grpc_arg arg;
diff --git a/test/cpp/end2end/round_robin_end2end_test.cc b/test/cpp/end2end/round_robin_end2end_test.cc
index 76211cbdd3e742cd827f772f4ed6b324cffa2066..cc340b96b34668ade39a675e3e71bc692f80c2e5 100644
--- a/test/cpp/end2end/round_robin_end2end_test.cc
+++ b/test/cpp/end2end/round_robin_end2end_test.cc
@@ -109,9 +109,9 @@ class RoundRobinEnd2endTest : public ::testing::Test {
       uri << "127.0.0.1:" << servers_[i]->port_ << ",";
     }
     uri << "127.0.0.1:" << servers_[servers_.size() - 1]->port_;
-    std::shared_ptr<Channel> channel =
+    channel_ =
         CreateCustomChannel(uri.str(), InsecureChannelCredentials(), args);
-    stub_ = grpc::testing::EchoTestService::NewStub(channel);
+    stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
   void SendRpc(int num_rpcs) {
@@ -165,6 +165,7 @@ class RoundRobinEnd2endTest : public ::testing::Test {
 
   const grpc::string server_host_;
   CompletionQueue cli_cq_;
+  std::shared_ptr<Channel> channel_;
   std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_;
   std::vector<std::unique_ptr<ServerData>> servers_;
 };
@@ -186,6 +187,8 @@ TEST_F(RoundRobinEnd2endTest, PickFirst) {
     }
   }
   EXPECT_TRUE(found);
+  // Check LB policy name for the channel.
+  EXPECT_EQ("pick_first", channel_->GetLoadBalancingPolicyName());
 }
 
 TEST_F(RoundRobinEnd2endTest, RoundRobin) {
@@ -198,6 +201,8 @@ TEST_F(RoundRobinEnd2endTest, RoundRobin) {
   for (size_t i = 0; i < servers_.size(); ++i) {
     EXPECT_EQ(1, servers_[i]->service_.request_count());
   }
+  // Check LB policy name for the channel.
+  EXPECT_EQ("round_robin", channel_->GetLoadBalancingPolicyName());
 }
 
 }  // namespace
diff --git a/test/cpp/grpclb/grpclb_test.cc b/test/cpp/grpclb/grpclb_test.cc
index 175786332b38e105ef3654015fcd2cf522236f4e..e5bf18991b335a84e9b66b9d35fe394881cecded 100644
--- a/test/cpp/grpclb/grpclb_test.cc
+++ b/test/cpp/grpclb/grpclb_test.cc
@@ -108,6 +108,7 @@ typedef struct server_fixture {
   grpc_completion_queue *cq;
   char *servers_hostport;
   int port;
+  const char *lb_token_prefix;
   gpr_thd_id tid;
   int num_calls_serviced;
 } server_fixture;
@@ -123,7 +124,8 @@ static void *tag(intptr_t t) { return (void *)t; }
 
 static grpc_slice build_response_payload_slice(
     const char *host, int *ports, size_t nports,
-    int64_t expiration_interval_secs, int32_t expiration_interval_nanos) {
+    int64_t expiration_interval_secs, int32_t expiration_interval_nanos,
+    const char *token_prefix) {
   // server_list {
   //   servers {
   //     ip_address: <in_addr/6 bytes of an IP>
@@ -150,15 +152,15 @@ static grpc_slice build_response_payload_slice(
     struct in_addr ip4;
     GPR_ASSERT(inet_pton(AF_INET, host, &ip4) == 1);
     server->set_ip_address(
-        grpc::string(reinterpret_cast<const char *>(&ip4), sizeof(ip4)));
+        string(reinterpret_cast<const char *>(&ip4), sizeof(ip4)));
     server->set_port(ports[i]);
-    // The following long long int cast is meant to work around the
-    // disfunctional implementation of std::to_string in gcc 4.4, which doesn't
-    // have a version for int but does have one for long long int.
-    string token_data = "token" + std::to_string((long long int)ports[i]);
-    server->set_load_balance_token(token_data);
+    // Missing tokens are acceptable. Test that path.
+    if (strlen(token_prefix) > 0) {
+      string token_data = token_prefix + std::to_string(ports[i]);
+      server->set_load_balance_token(token_data);
+    }
   }
-  const grpc::string &enc_resp = response.SerializeAsString();
+  const string &enc_resp = response.SerializeAsString();
   return grpc_slice_from_copied_buffer(enc_resp.data(), enc_resp.size());
 }
 
@@ -250,14 +252,14 @@ static void start_lb_server(server_fixture *sf, int *ports, size_t nports,
   for (int i = 0; i < 2; i++) {
     if (i == 0) {
       // First half of the ports.
-      response_payload_slice =
-          build_response_payload_slice("127.0.0.1", ports, nports / 2, -1, -1);
+      response_payload_slice = build_response_payload_slice(
+          "127.0.0.1", ports, nports / 2, -1, -1, sf->lb_token_prefix);
     } else {
       // Second half of the ports.
       sleep_ms(update_delay_ms);
-      response_payload_slice =
-          build_response_payload_slice("127.0.0.1", ports + (nports / 2),
-                                       (nports + 1) / 2 /* ceil */, -1, -1);
+      response_payload_slice = build_response_payload_slice(
+          "127.0.0.1", ports + (nports / 2), (nports + 1) / 2 /* ceil */, -1,
+          -1, "" /* this half doesn't get to receive an LB token */);
     }
 
     response_payload = grpc_raw_byte_buffer_create(&response_payload_slice, 1);
@@ -339,11 +341,10 @@ static void start_backend_server(server_fixture *sf) {
       return;
     }
     GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-
-    // The following long long int cast is meant to work around the
-    // disfunctional implementation of std::to_string in gcc 4.4, which doesn't
-    // have a version for int but does have one for long long int.
-    string expected_token = "token" + std::to_string((long long int)sf->port);
+    const string expected_token =
+        strlen(sf->lb_token_prefix) == 0
+            ? ""
+            : sf->lb_token_prefix + std::to_string(sf->port);
     GPR_ASSERT(contains_metadata(&request_metadata_recv, "lb-token",
                                  expected_token.c_str()));
 
@@ -626,6 +627,7 @@ static void fork_lb_server(void *arg) {
                   tf->lb_server_update_delay_ms);
 }
 
+#define LB_TOKEN_PREFIX "token"
 static test_fixture setup_test_fixture(int lb_server_update_delay_ms) {
   test_fixture tf;
   memset(&tf, 0, sizeof(tf));
@@ -635,11 +637,18 @@ static test_fixture setup_test_fixture(int lb_server_update_delay_ms) {
   gpr_thd_options_set_joinable(&options);
 
   for (int i = 0; i < NUM_BACKENDS; ++i) {
+    // Only the first half of the servers expect an LB token.
+    if (i < NUM_BACKENDS / 2) {
+      tf.lb_backends[i].lb_token_prefix = LB_TOKEN_PREFIX;
+    } else {
+      tf.lb_backends[i].lb_token_prefix = "";
+    }
     setup_server("127.0.0.1", &tf.lb_backends[i]);
     gpr_thd_new(&tf.lb_backends[i].tid, fork_backend_server, &tf.lb_backends[i],
                 &options);
   }
 
+  tf.lb_server.lb_token_prefix = LB_TOKEN_PREFIX;
   setup_server("127.0.0.1", &tf.lb_server);
   gpr_thd_new(&tf.lb_server.tid, fork_lb_server, &tf.lb_server, &options);
 
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 6c40c0f2f6faf4edfad099fb9f151e7df70f2ce0..bf19ebcfb6af872d90f7c4079d9f3faecfb67679 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -880,8 +880,8 @@ src/core/lib/transport/byte_stream.h \
 src/core/lib/transport/connectivity_state.h \
 src/core/lib/transport/metadata.h \
 src/core/lib/transport/metadata_batch.h \
-src/core/lib/transport/method_config.h \
 src/core/lib/transport/pid_controller.h \
+src/core/lib/transport/service_config.h \
 src/core/lib/transport/static_metadata.h \
 src/core/lib/transport/timeout_encoding.h \
 src/core/lib/transport/transport.h \
@@ -1074,8 +1074,8 @@ src/core/lib/transport/byte_stream.c \
 src/core/lib/transport/connectivity_state.c \
 src/core/lib/transport/metadata.c \
 src/core/lib/transport/metadata_batch.c \
-src/core/lib/transport/method_config.c \
 src/core/lib/transport/pid_controller.c \
+src/core/lib/transport/service_config.c \
 src/core/lib/transport/static_metadata.c \
 src/core/lib/transport/timeout_encoding.c \
 src/core/lib/transport/transport.c \
diff --git a/tools/jenkins/run_full_performance.sh b/tools/jenkins/run_full_performance.sh
index 4c4bddb8550bfe73791c5ee9b68840f672bbcd7b..aa8b4f7281a76555fca9979b8874ba3fb4e038f1 100755
--- a/tools/jenkins/run_full_performance.sh
+++ b/tools/jenkins/run_full_performance.sh
@@ -41,8 +41,12 @@ tools/run_tests/run_performance_tests.py \
     --category scalable \
     --bq_result_table performance_test.performance_experiment \
     --remote_worker_host grpc-performance-server-8core grpc-performance-client-8core grpc-performance-client2-8core \
+    --xml_report report_8core.xml \
     || EXIT_CODE=1
 
+# prevent pushing leftover build files to remote hosts in the next step.
+git clean -fdxq --exclude='report*.xml'
+
 # scalability with 32cores (and upload to a different BQ table)
 tools/run_tests/run_performance_tests.py \
     -l c++ java csharp go \
@@ -50,6 +54,19 @@ tools/run_tests/run_performance_tests.py \
     --category scalable \
     --bq_result_table performance_test.performance_experiment_32core \
     --remote_worker_host grpc-performance-server-32core grpc-performance-client-32core grpc-performance-client2-32core \
+    --xml_report report_32core.xml \
+    || EXIT_CODE=1
+
+# prevent pushing leftover build files to remote hosts in the next step.
+git clean -fdxq --exclude='report*.xml'
+
+# selected scenarios on Windows
+tools/run_tests/run_performance_tests.py \
+    -l csharp \
+    --category scalable \
+    --bq_result_table performance_test.performance_experiment_windows \
+    --remote_worker_host grpc-performance-windows1 grpc-performance-windows2 \
+    --xml_report report_windows.xml \
     || EXIT_CODE=1
 
 exit $EXIT_CODE
diff --git a/tools/run_tests/performance/build_performance.sh b/tools/run_tests/performance/build_performance.sh
index e981cae76b012d10d8f571777fcae396ae51113b..5f8749dda2725f9a30b9ced02ef0387d568e5e36 100755
--- a/tools/run_tests/performance/build_performance.sh
+++ b/tools/run_tests/performance/build_performance.sh
@@ -37,10 +37,14 @@ CONFIG=${CONFIG:-opt}
 
 # build C++ qps worker & driver always - we need at least the driver to
 # run any of the scenarios.
-# TODO(jtattermusch): not embedding OpenSSL breaks the C# build because
-# grpc_csharp_ext needs OpenSSL embedded and some intermediate files from
-# this build will be reused.
-make CONFIG=${CONFIG} EMBED_OPENSSL=true EMBED_ZLIB=true qps_worker qps_json_driver -j8
+# TODO(jtattermusch): C++ worker and driver are not buildable on Windows yet
+if [ "$OSTYPE" != "msys" ]
+then
+  # TODO(jtattermusch): not embedding OpenSSL breaks the C# build because
+  # grpc_csharp_ext needs OpenSSL embedded and some intermediate files from
+  # this build will be reused.
+  make CONFIG=${CONFIG} EMBED_OPENSSL=true EMBED_ZLIB=true qps_worker qps_json_driver -j8
+fi
 
 for language in $@
 do
@@ -55,10 +59,10 @@ do
     tools/run_tests/performance/build_performance_go.sh
     ;;
   "csharp")
-    tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8 --compiler coreclr
+    python tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8 --compiler coreclr
     ;;
   *)
-    tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8
+    python tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8
     ;;
   esac
 done
diff --git a/tools/run_tests/performance/remote_host_prepare.sh b/tools/run_tests/performance/remote_host_prepare.sh
index f81102bbdc4facf2962cc62f161bd874cf589256..d6d09b6cc887e7f2f316069e8665484c614e7db7 100755
--- a/tools/run_tests/performance/remote_host_prepare.sh
+++ b/tools/run_tests/performance/remote_host_prepare.sh
@@ -32,18 +32,23 @@ set -ex
 
 cd $(dirname $0)/../../..
 
-# cleanup after previous builds
-ssh "${USER_AT_HOST}" "rm -rf ~/performance_workspace && mkdir -p ~/performance_workspace"
-
 # TODO(jtattermusch): To be sure there are no running processes that would
 # mess with the results, be rough and reboot the slave here
 # and wait for it to come back online.
-# could also kill jenkins.
-ssh "${USER_AT_HOST}" "killall -9 qps_worker mono node ruby worker || true"
+ssh "${USER_AT_HOST}" "killall -9 qps_worker dotnet mono node ruby worker || true"
+
+# On Windows, killall is not supported & we need to kill all pending workers
+# before attempting to delete the workspace
+ssh "${USER_AT_HOST}" "ps -e | egrep 'qps_worker|dotnet' | awk '{print $1}' | xargs kill -9 || true"
+
+# cleanup after previous builds
+ssh "${USER_AT_HOST}" "rm -rf ~/performance_workspace && mkdir -p ~/performance_workspace"
 
 # push the current sources to the slave and unpack it.
 scp ../grpc.tar "${USER_AT_HOST}:~/performance_workspace"
-ssh "${USER_AT_HOST}" "tar -xf ~/performance_workspace/grpc.tar -C ~/performance_workspace"
+# Windows workaround: attempt to untar twice, first run is going to fail
+# with symlink creation error(s).
+ssh "${USER_AT_HOST}" "tar -xf ~/performance_workspace/grpc.tar -C ~/performance_workspace || tar -xf ~/performance_workspace/grpc.tar -C ~/performance_workspace"
 
 # For consistency with local run, invoke the kill_workers script remotely.
 ssh "${USER_AT_HOST}" "~/performance_workspace/grpc/tools/run_tests/performance/kill_workers.sh"
diff --git a/tools/run_tests/run_performance_tests.py b/tools/run_tests/run_performance_tests.py
index 7cb827ecea9c0c92bb57397931e024afbdc48e6c..1d0c98fb6903c39b64fa4d65c6d8a158cb60d699 100755
--- a/tools/run_tests/run_performance_tests.py
+++ b/tools/run_tests/run_performance_tests.py
@@ -403,6 +403,8 @@ argp.add_argument('--netperf',
                   action='store_const',
                   const=True,
                   help='Run netperf benchmark as one of the scenarios.')
+argp.add_argument('-x', '--xml_report', default='report.xml', type=str,
+                  help='Name of XML report file to generate.')
 
 args = argp.parse_args()
 
@@ -473,7 +475,7 @@ for scenario in scenarios:
       qps_workers_killed += finish_qps_workers(scenario.workers)
 
 
-report_utils.render_junit_xml_report(merged_resultset, 'report.xml',
+report_utils.render_junit_xml_report(merged_resultset, args.xml_report,
                                      suite_name='benchmarks')
 
 if total_scenario_failures > 0 or qps_workers_killed > 0:
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 092c3cd65c8d55afdce2fb4d0c7d503faeec5ea9..fa00ca214f6586560817119df3ca96eda100ad50 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -6763,8 +6763,8 @@
       "src/core/lib/transport/connectivity_state.h", 
       "src/core/lib/transport/metadata.h", 
       "src/core/lib/transport/metadata_batch.h", 
-      "src/core/lib/transport/method_config.h", 
       "src/core/lib/transport/pid_controller.h", 
+      "src/core/lib/transport/service_config.h", 
       "src/core/lib/transport/static_metadata.h", 
       "src/core/lib/transport/timeout_encoding.h", 
       "src/core/lib/transport/transport.h", 
@@ -6981,10 +6981,10 @@
       "src/core/lib/transport/metadata.h", 
       "src/core/lib/transport/metadata_batch.c", 
       "src/core/lib/transport/metadata_batch.h", 
-      "src/core/lib/transport/method_config.c", 
-      "src/core/lib/transport/method_config.h", 
       "src/core/lib/transport/pid_controller.c", 
       "src/core/lib/transport/pid_controller.h", 
+      "src/core/lib/transport/service_config.c", 
+      "src/core/lib/transport/service_config.h", 
       "src/core/lib/transport/static_metadata.c", 
       "src/core/lib/transport/static_metadata.h", 
       "src/core/lib/transport/timeout_encoding.c", 
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 24b8cedef60bd78963b4c17546d392ef373d1511..da24dd58e55cad7c369477519c0aceb1a78afcd4 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -389,8 +389,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\transport.h" />
@@ -693,10 +693,10 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index 0529f7d1fd44ce0516378e1f7180f119d3cf4fe5..9adff1667a552c84c546fdc4453d55e88bf8b2a9 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -325,10 +325,10 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
@@ -1007,10 +1007,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h">
diff --git a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
index b3e2be96503a712d34472b796efbac54eaf9d9e5..a9a5d72372d5fc35d800e9780ece7442de9c15fa 100644
--- a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
+++ b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
@@ -282,8 +282,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\transport.h" />
@@ -544,10 +544,10 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.c">
diff --git a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
index 4dbfa6b52ab762d782f5ca536008f1df36089cbb..ab79e8dc535ab68d52ff039d3b1e59067fbaf39d 100644
--- a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
@@ -382,10 +382,10 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
@@ -803,10 +803,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index ead1eef5611821dfe54d2e84324c8dbf30ef869f..1d6e4394f41fbcaa6abe7d7b8b58eb63e555277a 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -379,8 +379,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\connectivity_state.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\transport.h" />
@@ -661,10 +661,10 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\timeout_encoding.c">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index 94b7fb379fd7d0a1519051874d994e3ddc1dc148..3792df7a9c32042503f707f2d3a5a94b59387ebd 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -328,10 +328,10 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\method_config.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.c">
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\service_config.c">
       <Filter>src\core\lib\transport</Filter>
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.c">
@@ -920,10 +920,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\metadata_batch.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\method_config.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\pid_controller.h">
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\service_config.h">
       <Filter>src\core\lib\transport</Filter>
     </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\transport\static_metadata.h">