diff --git a/src/csharp/GrpcApi/proto/test.proto b/src/csharp/GrpcApi/proto/test.proto
index 8380ebb31de74fc7e68a83ac5fc7dbd4485372fb..996f11aa6df2535ff112b0cb26660f3beeb8a412 100644
--- a/src/csharp/GrpcApi/proto/test.proto
+++ b/src/csharp/GrpcApi/proto/test.proto
@@ -14,7 +14,7 @@ service TestService {
   rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
 
   // One request followed by one response.
-  // The server returns the client payload as-is.
+  // TODO(Issue 527): Describe required server behavior.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
   // One request followed by a sequence of responses (streamed download).
diff --git a/src/node/interop/test.proto b/src/node/interop/test.proto
index 8380ebb31de74fc7e68a83ac5fc7dbd4485372fb..996f11aa6df2535ff112b0cb26660f3beeb8a412 100644
--- a/src/node/interop/test.proto
+++ b/src/node/interop/test.proto
@@ -14,7 +14,7 @@ service TestService {
   rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
 
   // One request followed by one response.
-  // The server returns the client payload as-is.
+  // TODO(Issue 527): Describe required server behavior.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
   // One request followed by a sequence of responses (streamed download).
diff --git a/src/python/interop/interop/__init__.py b/src/python/interop/interop/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/python/interop/interop/credentials/README b/src/python/interop/interop/credentials/README
new file mode 100755
index 0000000000000000000000000000000000000000..cb20dcb49fbafe0f82ccda1544023605e4dfaaac
--- /dev/null
+++ b/src/python/interop/interop/credentials/README
@@ -0,0 +1 @@
+These are test keys *NOT* to be used in production.
diff --git a/src/python/interop/interop/credentials/server1.key b/src/python/interop/interop/credentials/server1.key
new file mode 100755
index 0000000000000000000000000000000000000000..143a5b87658d5c3519de970f37efd1c32a98223d
--- /dev/null
+++ b/src/python/interop/interop/credentials/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/src/python/interop/interop/credentials/server1.pem b/src/python/interop/interop/credentials/server1.pem
new file mode 100755
index 0000000000000000000000000000000000000000..8e582e571f643ca3fb6042a786c709a53f5afcf3
--- /dev/null
+++ b/src/python/interop/interop/credentials/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
+MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
+c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
+JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
+RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
+3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
+BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
+b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
+KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
+wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
+aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
+-----END CERTIFICATE-----
diff --git a/src/python/interop/interop/empty_pb2.py b/src/python/interop/interop/empty_pb2.py
new file mode 100644
index 0000000000000000000000000000000000000000..753341c7daf8be7ebc34f9ef9c4b3edcb1c9a899
--- /dev/null
+++ b/src/python/interop/interop/empty_pb2.py
@@ -0,0 +1,60 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: test/cpp/interop/empty.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='test/cpp/interop/empty.proto',
+  package='grpc.testing',
+  serialized_pb=_b('\n\x1ctest/cpp/interop/empty.proto\x12\x0cgrpc.testing\"\x07\n\x05\x45mpty')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+
+_EMPTY = _descriptor.Descriptor(
+  name='Empty',
+  full_name='grpc.testing.Empty',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=46,
+  serialized_end=53,
+)
+
+DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY
+
+Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), dict(
+  DESCRIPTOR = _EMPTY,
+  __module__ = 'test.cpp.interop.empty_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.Empty)
+  ))
+_sym_db.RegisterMessage(Empty)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/python/interop/interop/messages_pb2.py b/src/python/interop/interop/messages_pb2.py
new file mode 100644
index 0000000000000000000000000000000000000000..79270cdf1292bf0ef76ac72166f6240e337b2f25
--- /dev/null
+++ b/src/python/interop/interop/messages_pb2.py
@@ -0,0 +1,444 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: test/cpp/interop/messages.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='test/cpp/interop/messages.proto',
+  package='grpc.testing',
+  serialized_pb=_b('\n\x1ftest/cpp/interop/messages.proto\x12\x0cgrpc.testing\"@\n\x07Payload\x12\'\n\x04type\x18\x01 \x01(\x0e\x32\x19.grpc.testing.PayloadType\x12\x0c\n\x04\x62ody\x18\x02 \x01(\x0c\"\xb1\x01\n\rSimpleRequest\x12\x30\n\rresponse_type\x18\x01 \x01(\x0e\x32\x19.grpc.testing.PayloadType\x12\x15\n\rresponse_size\x18\x02 \x01(\x05\x12&\n\x07payload\x18\x03 \x01(\x0b\x32\x15.grpc.testing.Payload\x12\x15\n\rfill_username\x18\x04 \x01(\x08\x12\x18\n\x10\x66ill_oauth_scope\x18\x05 \x01(\x08\"_\n\x0eSimpleResponse\x12&\n\x07payload\x18\x01 \x01(\x0b\x32\x15.grpc.testing.Payload\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x13\n\x0boauth_scope\x18\x03 \x01(\t\"C\n\x19StreamingInputCallRequest\x12&\n\x07payload\x18\x01 \x01(\x0b\x32\x15.grpc.testing.Payload\"=\n\x1aStreamingInputCallResponse\x12\x1f\n\x17\x61ggregated_payload_size\x18\x01 \x01(\x05\"7\n\x12ResponseParameters\x12\x0c\n\x04size\x18\x01 \x01(\x05\x12\x13\n\x0binterval_us\x18\x02 \x01(\x05\"\xb5\x01\n\x1aStreamingOutputCallRequest\x12\x30\n\rresponse_type\x18\x01 \x01(\x0e\x32\x19.grpc.testing.PayloadType\x12=\n\x13response_parameters\x18\x02 \x03(\x0b\x32 .grpc.testing.ResponseParameters\x12&\n\x07payload\x18\x03 \x01(\x0b\x32\x15.grpc.testing.Payload\"E\n\x1bStreamingOutputCallResponse\x12&\n\x07payload\x18\x01 \x01(\x0b\x32\x15.grpc.testing.Payload*?\n\x0bPayloadType\x12\x10\n\x0c\x43OMPRESSABLE\x10\x00\x12\x12\n\x0eUNCOMPRESSABLE\x10\x01\x12\n\n\x06RANDOM\x10\x02')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+_PAYLOADTYPE = _descriptor.EnumDescriptor(
+  name='PayloadType',
+  full_name='grpc.testing.PayloadType',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='COMPRESSABLE', index=0, number=0,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='UNCOMPRESSABLE', index=1, number=1,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RANDOM', index=2, number=2,
+      options=None,
+      type=None),
+  ],
+  containing_type=None,
+  options=None,
+  serialized_start=836,
+  serialized_end=899,
+)
+_sym_db.RegisterEnumDescriptor(_PAYLOADTYPE)
+
+PayloadType = enum_type_wrapper.EnumTypeWrapper(_PAYLOADTYPE)
+COMPRESSABLE = 0
+UNCOMPRESSABLE = 1
+RANDOM = 2
+
+
+
+_PAYLOAD = _descriptor.Descriptor(
+  name='Payload',
+  full_name='grpc.testing.Payload',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='type', full_name='grpc.testing.Payload.type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='body', full_name='grpc.testing.Payload.body', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=49,
+  serialized_end=113,
+)
+
+
+_SIMPLEREQUEST = _descriptor.Descriptor(
+  name='SimpleRequest',
+  full_name='grpc.testing.SimpleRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='response_type', full_name='grpc.testing.SimpleRequest.response_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='response_size', full_name='grpc.testing.SimpleRequest.response_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='grpc.testing.SimpleRequest.payload', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='fill_username', full_name='grpc.testing.SimpleRequest.fill_username', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='fill_oauth_scope', full_name='grpc.testing.SimpleRequest.fill_oauth_scope', index=4,
+      number=5, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=116,
+  serialized_end=293,
+)
+
+
+_SIMPLERESPONSE = _descriptor.Descriptor(
+  name='SimpleResponse',
+  full_name='grpc.testing.SimpleResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='grpc.testing.SimpleResponse.payload', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='username', full_name='grpc.testing.SimpleResponse.username', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='oauth_scope', full_name='grpc.testing.SimpleResponse.oauth_scope', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=295,
+  serialized_end=390,
+)
+
+
+_STREAMINGINPUTCALLREQUEST = _descriptor.Descriptor(
+  name='StreamingInputCallRequest',
+  full_name='grpc.testing.StreamingInputCallRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='grpc.testing.StreamingInputCallRequest.payload', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=392,
+  serialized_end=459,
+)
+
+
+_STREAMINGINPUTCALLRESPONSE = _descriptor.Descriptor(
+  name='StreamingInputCallResponse',
+  full_name='grpc.testing.StreamingInputCallResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='aggregated_payload_size', full_name='grpc.testing.StreamingInputCallResponse.aggregated_payload_size', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=461,
+  serialized_end=522,
+)
+
+
+_RESPONSEPARAMETERS = _descriptor.Descriptor(
+  name='ResponseParameters',
+  full_name='grpc.testing.ResponseParameters',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='size', full_name='grpc.testing.ResponseParameters.size', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='interval_us', full_name='grpc.testing.ResponseParameters.interval_us', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=524,
+  serialized_end=579,
+)
+
+
+_STREAMINGOUTPUTCALLREQUEST = _descriptor.Descriptor(
+  name='StreamingOutputCallRequest',
+  full_name='grpc.testing.StreamingOutputCallRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='response_type', full_name='grpc.testing.StreamingOutputCallRequest.response_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='response_parameters', full_name='grpc.testing.StreamingOutputCallRequest.response_parameters', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='grpc.testing.StreamingOutputCallRequest.payload', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=582,
+  serialized_end=763,
+)
+
+
+_STREAMINGOUTPUTCALLRESPONSE = _descriptor.Descriptor(
+  name='StreamingOutputCallResponse',
+  full_name='grpc.testing.StreamingOutputCallResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='grpc.testing.StreamingOutputCallResponse.payload', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=765,
+  serialized_end=834,
+)
+
+_PAYLOAD.fields_by_name['type'].enum_type = _PAYLOADTYPE
+_SIMPLEREQUEST.fields_by_name['response_type'].enum_type = _PAYLOADTYPE
+_SIMPLEREQUEST.fields_by_name['payload'].message_type = _PAYLOAD
+_SIMPLERESPONSE.fields_by_name['payload'].message_type = _PAYLOAD
+_STREAMINGINPUTCALLREQUEST.fields_by_name['payload'].message_type = _PAYLOAD
+_STREAMINGOUTPUTCALLREQUEST.fields_by_name['response_type'].enum_type = _PAYLOADTYPE
+_STREAMINGOUTPUTCALLREQUEST.fields_by_name['response_parameters'].message_type = _RESPONSEPARAMETERS
+_STREAMINGOUTPUTCALLREQUEST.fields_by_name['payload'].message_type = _PAYLOAD
+_STREAMINGOUTPUTCALLRESPONSE.fields_by_name['payload'].message_type = _PAYLOAD
+DESCRIPTOR.message_types_by_name['Payload'] = _PAYLOAD
+DESCRIPTOR.message_types_by_name['SimpleRequest'] = _SIMPLEREQUEST
+DESCRIPTOR.message_types_by_name['SimpleResponse'] = _SIMPLERESPONSE
+DESCRIPTOR.message_types_by_name['StreamingInputCallRequest'] = _STREAMINGINPUTCALLREQUEST
+DESCRIPTOR.message_types_by_name['StreamingInputCallResponse'] = _STREAMINGINPUTCALLRESPONSE
+DESCRIPTOR.message_types_by_name['ResponseParameters'] = _RESPONSEPARAMETERS
+DESCRIPTOR.message_types_by_name['StreamingOutputCallRequest'] = _STREAMINGOUTPUTCALLREQUEST
+DESCRIPTOR.message_types_by_name['StreamingOutputCallResponse'] = _STREAMINGOUTPUTCALLRESPONSE
+DESCRIPTOR.enum_types_by_name['PayloadType'] = _PAYLOADTYPE
+
+Payload = _reflection.GeneratedProtocolMessageType('Payload', (_message.Message,), dict(
+  DESCRIPTOR = _PAYLOAD,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.Payload)
+  ))
+_sym_db.RegisterMessage(Payload)
+
+SimpleRequest = _reflection.GeneratedProtocolMessageType('SimpleRequest', (_message.Message,), dict(
+  DESCRIPTOR = _SIMPLEREQUEST,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.SimpleRequest)
+  ))
+_sym_db.RegisterMessage(SimpleRequest)
+
+SimpleResponse = _reflection.GeneratedProtocolMessageType('SimpleResponse', (_message.Message,), dict(
+  DESCRIPTOR = _SIMPLERESPONSE,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.SimpleResponse)
+  ))
+_sym_db.RegisterMessage(SimpleResponse)
+
+StreamingInputCallRequest = _reflection.GeneratedProtocolMessageType('StreamingInputCallRequest', (_message.Message,), dict(
+  DESCRIPTOR = _STREAMINGINPUTCALLREQUEST,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallRequest)
+  ))
+_sym_db.RegisterMessage(StreamingInputCallRequest)
+
+StreamingInputCallResponse = _reflection.GeneratedProtocolMessageType('StreamingInputCallResponse', (_message.Message,), dict(
+  DESCRIPTOR = _STREAMINGINPUTCALLRESPONSE,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallResponse)
+  ))
+_sym_db.RegisterMessage(StreamingInputCallResponse)
+
+ResponseParameters = _reflection.GeneratedProtocolMessageType('ResponseParameters', (_message.Message,), dict(
+  DESCRIPTOR = _RESPONSEPARAMETERS,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.ResponseParameters)
+  ))
+_sym_db.RegisterMessage(ResponseParameters)
+
+StreamingOutputCallRequest = _reflection.GeneratedProtocolMessageType('StreamingOutputCallRequest', (_message.Message,), dict(
+  DESCRIPTOR = _STREAMINGOUTPUTCALLREQUEST,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallRequest)
+  ))
+_sym_db.RegisterMessage(StreamingOutputCallRequest)
+
+StreamingOutputCallResponse = _reflection.GeneratedProtocolMessageType('StreamingOutputCallResponse', (_message.Message,), dict(
+  DESCRIPTOR = _STREAMINGOUTPUTCALLRESPONSE,
+  __module__ = 'test.cpp.interop.messages_pb2'
+  # @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallResponse)
+  ))
+_sym_db.RegisterMessage(StreamingOutputCallResponse)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/python/interop/interop/methods.py b/src/python/interop/interop/methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5ce5902ca773b411685c3a7b5d4cf741bf8e26f
--- /dev/null
+++ b/src/python/interop/interop/methods.py
@@ -0,0 +1,109 @@
+# 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.
+
+"""Implementations of interoperability test methods."""
+
+from grpc_early_adopter import utilities
+
+from interop import empty_pb2
+from interop import messages_pb2
+
+def _empty_call(request):
+  return empty_pb2.Empty()
+
+EMPTY_CALL = utilities.unary_unary_rpc_method(
+    _empty_call, empty_pb2.Empty.SerializeToString, empty_pb2.Empty.FromString,
+    empty_pb2.Empty.SerializeToString, empty_pb2.Empty.FromString)
+
+
+def _unary_call(request):
+  return messages_pb2.SimpleResponse(
+      payload=messages_pb2.Payload(
+          type=messages_pb2.COMPRESSABLE,
+          body=b'\x00' * request.response_size))
+
+UNARY_CALL = utilities.unary_unary_rpc_method(
+    _unary_call, messages_pb2.SimpleRequest.SerializeToString,
+    messages_pb2.SimpleRequest.FromString,
+    messages_pb2.SimpleResponse.SerializeToString,
+    messages_pb2.SimpleResponse.FromString)
+
+
+def _streaming_output_call(request):
+  for response_parameters in request.response_parameters:
+    yield messages_pb2.StreamingOutputCallResponse(
+        payload=messages_pb2.Payload(
+            type=request.response_type,
+            body=b'\x00' * response_parameters.size))
+
+STREAMING_OUTPUT_CALL = utilities.unary_stream_rpc_method(
+    _streaming_output_call,
+    messages_pb2.StreamingOutputCallRequest.SerializeToString,
+    messages_pb2.StreamingOutputCallRequest.FromString,
+    messages_pb2.StreamingOutputCallResponse.SerializeToString,
+    messages_pb2.StreamingOutputCallResponse.FromString)
+
+
+def _streaming_input_call(request_iterator):
+  aggregate_size = 0
+  for request in request_iterator:
+    if request.payload and request.payload.body:
+      aggregate_size += len(request.payload.body)
+  return messages_pb2.StreamingInputCallResponse(
+      aggregated_payload_size=aggregate_size)
+
+STREAMING_INPUT_CALL = utilities.stream_unary_rpc_method(
+    _streaming_input_call,
+    messages_pb2.StreamingInputCallRequest.SerializeToString,
+    messages_pb2.StreamingInputCallRequest.FromString,
+    messages_pb2.StreamingInputCallResponse.SerializeToString,
+    messages_pb2.StreamingInputCallResponse.FromString)
+
+
+def _full_duplex_call(request_iterator):
+  for request in request_iterator:
+    yield messages_pb2.StreamingOutputCallResponse(
+        payload=messages_pb2.Payload(
+            type=request.payload.type,
+            body=b'\x00' * request.response_parameters[0].size))
+
+FULL_DUPLEX_CALL = utilities.stream_stream_rpc_method(
+    _full_duplex_call,
+    messages_pb2.StreamingOutputCallRequest.SerializeToString,
+    messages_pb2.StreamingOutputCallRequest.FromString,
+    messages_pb2.StreamingOutputCallResponse.SerializeToString,
+    messages_pb2.StreamingOutputCallResponse.FromString)
+
+# NOTE(nathaniel): Apparently this is the same as the full-duplex call?
+HALF_DUPLEX_CALL = utilities.stream_stream_rpc_method(
+    _full_duplex_call,
+    messages_pb2.StreamingOutputCallRequest.SerializeToString,
+    messages_pb2.StreamingOutputCallRequest.FromString,
+    messages_pb2.StreamingOutputCallResponse.SerializeToString,
+    messages_pb2.StreamingOutputCallResponse.FromString)
diff --git a/src/python/interop/interop/server.py b/src/python/interop/interop/server.py
new file mode 100644
index 0000000000000000000000000000000000000000..404c87dd0a935962fea1a98111ed1db5a51efa79
--- /dev/null
+++ b/src/python/interop/interop/server.py
@@ -0,0 +1,91 @@
+# 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.
+
+"""The Python implementation of the GRPC interoperability test server."""
+
+import argparse
+import logging
+import pkg_resources
+import time
+
+from grpc_early_adopter import implementations
+
+from interop import methods
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+
+_PRIVATE_KEY_RESOURCE_PATH = 'credentials/server1.key'
+_CERTIFICATE_CHAIN_RESOURCE_PATH = 'credentials/server1.pem'
+
+_METHODS = {
+    '/grpc.testing.TestService/EmptyCall': methods.EMPTY_CALL,
+    '/grpc.testing.TestService/UnaryCall': methods.UNARY_CALL,
+    '/grpc.testing.TestService/StreamingOutputCall':
+        methods.STREAMING_OUTPUT_CALL,
+    '/grpc.testing.TestService/StreamingInputCall':
+        methods.STREAMING_INPUT_CALL,
+    '/grpc.testing.TestService/FullDuplexCall':
+        methods.FULL_DUPLEX_CALL,
+    '/grpc.testing.TestService/HalfDuplexCall':
+        methods.HALF_DUPLEX_CALL,
+}
+
+
+def serve():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--port', help='the port on which to serve', type=int)
+  parser.add_argument(
+      '--use_tls', help='require a secure connection', dest='use_tls',
+      action='store_true')
+  args = parser.parse_args()
+
+  if args.use_tls:
+    private_key = pkg_resources.resource_string(
+        __name__, _PRIVATE_KEY_RESOURCE_PATH)
+    certificate_chain = pkg_resources.resource_string(
+        __name__, _CERTIFICATE_CHAIN_RESOURCE_PATH)
+    server = implementations.secure_server(
+        _METHODS, args.port, private_key, certificate_chain)
+  else:
+    server = implementations.insecure_server(
+        _METHODS, args.port)
+
+  server.start()
+  logging.info('Server serving.')
+  try:
+    while True:
+      time.sleep(_ONE_DAY_IN_SECONDS)
+  except BaseException as e:
+    logging.info('Caught exception "%s"; stopping server...', e)
+    server.stop()
+    logging.info('Server stopped; exiting.')
+
+if __name__ == '__main__':
+  serve()
diff --git a/src/python/interop/interop/test_pb2.py b/src/python/interop/interop/test_pb2.py
new file mode 100644
index 0000000000000000000000000000000000000000..124145315981776fed215c6071ebf5d9aed38396
--- /dev/null
+++ b/src/python/interop/interop/test_pb2.py
@@ -0,0 +1,32 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: test/cpp/interop/test.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from test.cpp.interop import empty_pb2 as test_dot_cpp_dot_interop_dot_empty__pb2
+from test.cpp.interop import messages_pb2 as test_dot_cpp_dot_interop_dot_messages__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='test/cpp/interop/test.proto',
+  package='grpc.testing',
+  serialized_pb=_b('\n\x1btest/cpp/interop/test.proto\x12\x0cgrpc.testing\x1a\x1ctest/cpp/interop/empty.proto\x1a\x1ftest/cpp/interop/messages.proto2\xbb\x04\n\x0bTestService\x12\x35\n\tEmptyCall\x12\x13.grpc.testing.Empty\x1a\x13.grpc.testing.Empty\x12\x46\n\tUnaryCall\x12\x1b.grpc.testing.SimpleRequest\x1a\x1c.grpc.testing.SimpleResponse\x12l\n\x13StreamingOutputCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse0\x01\x12i\n\x12StreamingInputCall\x12\'.grpc.testing.StreamingInputCallRequest\x1a(.grpc.testing.StreamingInputCallResponse(\x01\x12i\n\x0e\x46ullDuplexCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse(\x01\x30\x01\x12i\n\x0eHalfDuplexCall\x12(.grpc.testing.StreamingOutputCallRequest\x1a).grpc.testing.StreamingOutputCallResponse(\x01\x30\x01')
+  ,
+  dependencies=[test_dot_cpp_dot_interop_dot_empty__pb2.DESCRIPTOR,test_dot_cpp_dot_interop_dot_messages__pb2.DESCRIPTOR,])
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/python/interop/setup.py b/src/python/interop/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b7709f234805ea66eff94fb47829d6817991901
--- /dev/null
+++ b/src/python/interop/setup.py
@@ -0,0 +1,51 @@
+# 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.
+
+"""A setup module for the GRPC Python interop testing package."""
+
+from distutils import core as _core
+
+_PACKAGES = (
+    'interop',
+)
+
+_PACKAGE_DIRECTORIES = {
+    'interop': 'interop',
+}
+
+_PACKAGE_DATA = {
+    'interop': ['credentials/server1.key', 'credentials/server1.pem',]
+}
+
+_INSTALL_REQUIRES = ['grpc-2015>=0.0.1']
+
+_core.setup(
+    name='interop', version='0.0.1', packages=_PACKAGES,
+    package_dir=_PACKAGE_DIRECTORIES, package_data=_PACKAGE_DATA,
+    install_requires=_INSTALL_REQUIRES)
diff --git a/src/python/src/setup.py b/src/python/src/setup.py
index be3724026d5ee6da7dfec7f2a8a04583c60cdd79..93af4d68ca9e3e54ad4bb1a3db65fa25156bc67b 100644
--- a/src/python/src/setup.py
+++ b/src/python/src/setup.py
@@ -77,6 +77,6 @@ _PACKAGE_DIRECTORIES = {
 }
 
 _core.setup(
-    name='grpc', version='0.0.1',
+    name='grpc-2015', version='0.0.1',
     ext_modules=[_EXTENSION_MODULE], packages=_PACKAGES,
     package_dir=_PACKAGE_DIRECTORIES)
diff --git a/test/cpp/interop/test.proto b/test/cpp/interop/test.proto
index e358f3bea5b1c447fdcd6f67abcfd9162ab14501..1162ad61245930b20ecf210380226705b4a677da 100644
--- a/test/cpp/interop/test.proto
+++ b/test/cpp/interop/test.proto
@@ -14,7 +14,7 @@ service TestService {
   rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
 
   // One request followed by one response.
-  // The server returns the client payload as-is.
+  // TODO(Issue 527): Describe required server behavior.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
   // One request followed by a sequence of responses (streamed download).