diff --git a/src/core/ext/client_channel/client_channel.c b/src/core/ext/client_channel/client_channel.c
index ff773ac33489d0c21cc26a3e6f6bd1c2c316322b..e569af68e93b7265cf39f105ee3d9235a84f284d 100644
--- a/src/core/ext/client_channel/client_channel.c
+++ b/src/core/ext/client_channel/client_channel.c
@@ -94,17 +94,44 @@ static int method_parameters_cmp(void *value1, void *value2) {
 static const grpc_mdstr_hash_table_vtable method_parameters_vtable = {
     gpr_free, method_parameters_copy, method_parameters_cmp};
 
-static void *method_config_convert_value(
-    const grpc_method_config *method_config) {
+static void *method_config_convert_value(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, "wait_for_ready") == 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;
+    } else if (strcmp(field->key, "timeout") == 0) {
+      if (timeout.tv_sec > 0 || timeout.tv_nsec > 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_OBJECT) return NULL;
+      if (field->child == NULL) return NULL;
+      for (grpc_json* subfield = field->child; subfield != NULL;
+           subfield = subfield->next) {
+        if (subfield->key == NULL) return NULL;
+        if (strcmp(subfield->key, "seconds") == 0) {
+          if (timeout.tv_sec > 0) return NULL;  // Duplicate.
+          if (subfield->type != GRPC_JSON_NUMBER) return NULL;
+          timeout.tv_sec = gpr_parse_nonnegative_number(subfield->value);
+          if (timeout.tv_sec == -1) return NULL;
+        } else if (strcmp(subfield->key, "nanos") == 0) {
+          if (timeout.tv_nsec > 0) return NULL;  // Duplicate.
+          if (subfield->type != GRPC_JSON_NUMBER) return NULL;
+          timeout.tv_nsec = gpr_parse_nonnegative_number(subfield->value);
+          if (timeout.tv_nsec == -1) return NULL;
+        } else {
+          // Unknown key.
+          return NULL;
+        }
+      }
+    }
+  }
   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;
 }
 
@@ -285,8 +312,8 @@ static void on_resolver_result_changed(grpc_exec_ctx *exec_ctx, void *arg,
         grpc_channel_args_find(lb_policy_args.args, GRPC_ARG_SERVICE_CONFIG);
     if (channel_arg != NULL) {
       GPR_ASSERT(channel_arg->type == GRPC_ARG_POINTER);
-      method_params_table = grpc_method_config_table_convert(
-          (grpc_method_config_table *)channel_arg->value.pointer.p,
+      method_params_table = grpc_method_config_table_create_from_json(
+          (grpc_json *)channel_arg->value.pointer.p,
           method_config_convert_value, &method_parameters_vtable);
     }
     grpc_channel_args_destroy(chand->resolver_result);
diff --git a/src/core/lib/channel/message_size_filter.c b/src/core/lib/channel/message_size_filter.c
index 7dc5ae0df150632aeee44d42f254c2dfbeb8f4b5..4723ab8098108327c625341d71774e04b7b9cf51 100644
--- a/src/core/lib/channel/message_size_filter.c
+++ b/src/core/lib/channel/message_size_filter.c
@@ -39,6 +39,7 @@
 #include <grpc/support/string_util.h>
 
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/support/string.h"
 #include "src/core/lib/transport/method_config.h"
 
 #define DEFAULT_MAX_SEND_MESSAGE_LENGTH -1  // Unlimited.
@@ -69,17 +70,26 @@ static int message_size_limits_cmp(void* value1, void* value2) {
 static const grpc_mdstr_hash_table_vtable message_size_limits_vtable = {
     gpr_free, message_size_limits_copy, message_size_limits_cmp};
 
-static void* method_config_convert_value(
-    const grpc_method_config* method_config) {
+static void* method_config_convert_value(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, "max_request_message_bytes") == 0) {
+      if (max_request_message_bytes >= 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_NUMBER) return NULL;
+      max_request_message_bytes = gpr_parse_nonnegative_number(field->value);
+      if (max_request_message_bytes == -1) return NULL;
+    } else if (strcmp(field->key, "max_response_message_bytes") == 0) {
+      if (max_response_message_bytes >= 0) return NULL;  // Duplicate.
+      if (field->type != GRPC_JSON_NUMBER) return NULL;
+      max_response_message_bytes = gpr_parse_nonnegative_number(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;
 }
 
@@ -224,8 +234,8 @@ static void init_channel_elem(grpc_exec_ctx* exec_ctx,
       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(
-        (grpc_method_config_table*)channel_arg->value.pointer.p,
+    chand->method_limit_table = grpc_method_config_table_create_from_json(
+        (grpc_json*)channel_arg->value.pointer.p,
         method_config_convert_value, &message_size_limits_vtable);
   }
 }
diff --git a/src/core/lib/support/string.c b/src/core/lib/support/string.c
index d17fb9da4b0bc8c7a4a58e65807da3739f6da97d..56f29492bf0279a849b5a4e5f4fb6c8928adfd3b 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>
@@ -194,6 +196,13 @@ int int64_ttoa(int64_t value, char *string) {
   return i;
 }
 
+int gpr_parse_nonnegative_number(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 9a94e9471cf9e1776027d8cfc20e70e304635274..3a5a8ed826f2d5578bbad07db051cbc7f86746ac 100644
--- a/src/core/lib/support/string.h
+++ b/src/core/lib/support/string.h
@@ -80,6 +80,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_number(const char* value);
+
 /* Reverse a run of bytes */
 void gpr_reverse_bytes(char *str, int len);
 
diff --git a/src/core/lib/transport/method_config.c b/src/core/lib/transport/method_config.c
index 57d97700bfe4f1f7c07b5f9ee2d13d707833594e..23cab2b96b0cdf316d8db4c0fa69c8356051f8eb 100644
--- a/src/core/lib/transport/method_config.c
+++ b/src/core/lib/transport/method_config.c
@@ -39,6 +39,8 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/json/json.h"
+#include "src/core/lib/support/string.h"
 #include "src/core/lib/transport/mdstr_hash_table.h"
 #include "src/core/lib/transport/metadata.h"
 
@@ -338,3 +340,123 @@ grpc_mdstr_hash_table* grpc_method_config_table_convert(
   // Return the new table.
   return new_table;
 }
+
+// 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 name 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.
+static bool parse_json_method_config(
+    grpc_json* json,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_mdstr_hash_table_vtable* vtable,
+    grpc_mdstr_hash_table_entry* entries, size_t *idx) {
+  // Construct value.
+  void* method_config = create_value(json);
+  if (method_config == NULL) return NULL;
+  // Construct list of paths.
+  bool retval = 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_mdstr_from_string(paths.strs[i]);
+    entries[*idx].value = vtable->copy_value(method_config);
+    entries[*idx].vtable = vtable;
+    ++*idx;
+  }
+  retval = true;
+done:
+  vtable->destroy_value(method_config);
+  gpr_strvec_destroy(&paths);
+  return retval;
+}
+
+grpc_mdstr_hash_table* grpc_method_config_table_create_from_json(
+    const grpc_json* json,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_mdstr_hash_table_vtable* vtable) {
+  // Traverse parsed JSON tree.
+  if (json->type != GRPC_JSON_OBJECT || json->key != NULL) return NULL;
+  size_t num_entries = 0;
+  grpc_mdstr_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, "method_config") == 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_method_config_table_entry));
+      size_t idx = 0;
+      for (grpc_json* method = field->child; method != NULL;
+           method = method->next) {
+        if (!parse_json_method_config(method, create_value, vtable, entries,
+                                      &idx)) {
+          return NULL;
+        }
+      }
+      GPR_ASSERT(idx == num_entries);
+    }
+  }
+  // Instantiate method config table.
+  grpc_mdstr_hash_table* method_config_table = NULL;
+  if (entries != NULL) {
+    method_config_table = grpc_mdstr_hash_table_create(num_entries, entries);
+    // Clean up.
+    for (size_t i = 0; i < num_entries; ++i) {
+      GRPC_MDSTR_UNREF(entries[i].key);
+      vtable->destroy_value(entries[i].value);
+    }
+    gpr_free(entries);
+  }
+  return method_config_table;
+}
diff --git a/src/core/lib/transport/method_config.h b/src/core/lib/transport/method_config.h
index 58fedd94366feeac2cde0b23af3fb23e7dfe6be3..eac05f8173abe28078668fcf10e2f2b2f7635097 100644
--- a/src/core/lib/transport/method_config.h
+++ b/src/core/lib/transport/method_config.h
@@ -37,6 +37,7 @@
 #include <grpc/impl/codegen/gpr_types.h>
 #include <grpc/impl/codegen/grpc_types.h>
 
+#include "src/core/lib/json/json.h"
 #include "src/core/lib/transport/mdstr_hash_table.h"
 #include "src/core/lib/transport/metadata.h"
 
@@ -133,4 +134,10 @@ grpc_mdstr_hash_table* grpc_method_config_table_convert(
     void* (*convert_value)(const grpc_method_config* method_config),
     const grpc_mdstr_hash_table_vtable* vtable);
 
+// FIXME: document
+grpc_mdstr_hash_table* grpc_method_config_table_create_from_json(
+    const grpc_json* json,
+    void* (*create_value)(const grpc_json* method_config_json),
+    const grpc_mdstr_hash_table_vtable* vtable);
+
 #endif /* GRPC_CORE_LIB_TRANSPORT_METHOD_CONFIG_H */