pydantic-rpc 0.7.0__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/PKG-INFO +286 -58
  2. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/README.md +277 -47
  3. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/pyproject.toml +5 -9
  4. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/__init__.py +12 -4
  5. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/core.py +686 -203
  6. pydantic_rpc-0.9.0/src/pydantic_rpc/decorators.py +138 -0
  7. pydantic_rpc-0.9.0/src/pydantic_rpc/options.py +134 -0
  8. pydantic_rpc-0.7.0/.github/workflows/release.yml +0 -35
  9. pydantic_rpc-0.7.0/.github/workflows/test.yml +0 -47
  10. pydantic_rpc-0.7.0/.gitignore +0 -24
  11. pydantic_rpc-0.7.0/.python-version +0 -1
  12. pydantic_rpc-0.7.0/LICENSE +0 -21
  13. pydantic_rpc-0.7.0/docs/mcp.md +0 -95
  14. pydantic_rpc-0.7.0/examples/README.md +0 -152
  15. pydantic_rpc-0.7.0/examples/agent_aio_grpc.py +0 -65
  16. pydantic_rpc-0.7.0/examples/agent_connecpy.py +0 -56
  17. pydantic_rpc-0.7.0/examples/asyncio_greeting.py +0 -22
  18. pydantic_rpc-0.7.0/examples/barservice.proto +0 -17
  19. pydantic_rpc-0.7.0/examples/barservice_pb2.py +0 -37
  20. pydantic_rpc-0.7.0/examples/barservice_pb2.pyi +0 -18
  21. pydantic_rpc-0.7.0/examples/barservice_pb2_grpc.py +0 -106
  22. pydantic_rpc-0.7.0/examples/foobar.py +0 -76
  23. pydantic_rpc-0.7.0/examples/foobar_client.py +0 -20
  24. pydantic_rpc-0.7.0/examples/fooservice.proto +0 -21
  25. pydantic_rpc-0.7.0/examples/fooservice_pb2.py +0 -45
  26. pydantic_rpc-0.7.0/examples/fooservice_pb2.pyi +0 -56
  27. pydantic_rpc-0.7.0/examples/fooservice_pb2_grpc.py +0 -106
  28. pydantic_rpc-0.7.0/examples/google/protobuf/duration.proto +0 -115
  29. pydantic_rpc-0.7.0/examples/google/protobuf/timestamp.proto +0 -144
  30. pydantic_rpc-0.7.0/examples/greeter.proto +0 -35
  31. pydantic_rpc-0.7.0/examples/greeter_client.py +0 -14
  32. pydantic_rpc-0.7.0/examples/greeter_connecpy.py +0 -124
  33. pydantic_rpc-0.7.0/examples/greeter_connecpy_client.py +0 -42
  34. pydantic_rpc-0.7.0/examples/greeter_pb2.py +0 -37
  35. pydantic_rpc-0.7.0/examples/greeter_pb2.pyi +0 -17
  36. pydantic_rpc-0.7.0/examples/greeter_pb2_grpc.py +0 -106
  37. pydantic_rpc-0.7.0/examples/greeter_sonora_client.py +0 -8
  38. pydantic_rpc-0.7.0/examples/greeting.py +0 -45
  39. pydantic_rpc-0.7.0/examples/greeting_asgi.py +0 -55
  40. pydantic_rpc-0.7.0/examples/greeting_connecpy.py +0 -44
  41. pydantic_rpc-0.7.0/examples/greeting_using_exsiting_pb2_modules.py +0 -23
  42. pydantic_rpc-0.7.0/examples/greeting_wsgi.py +0 -63
  43. pydantic_rpc-0.7.0/examples/mcp_debug_example.py +0 -74
  44. pydantic_rpc-0.7.0/examples/mcp_example.py +0 -129
  45. pydantic_rpc-0.7.0/examples/mcp_http_example.py +0 -125
  46. pydantic_rpc-0.7.0/examples/mcp_simple_calculator.py +0 -45
  47. pydantic_rpc-0.7.0/examples/olympicsagent.proto +0 -40
  48. pydantic_rpc-0.7.0/examples/olympicsagent_pb2.py +0 -41
  49. pydantic_rpc-0.7.0/examples/olympicsagent_pb2.pyi +0 -37
  50. pydantic_rpc-0.7.0/examples/olympicsagent_pb2_grpc.py +0 -155
  51. pydantic_rpc-0.7.0/examples/olympicslocationagent.proto +0 -24
  52. pydantic_rpc-0.7.0/examples/olympicslocationagent_connecpy.py +0 -113
  53. pydantic_rpc-0.7.0/examples/olympicslocationagent_pb2.py +0 -39
  54. pydantic_rpc-0.7.0/examples/olympicslocationagent_pb2.pyi +0 -21
  55. pydantic_rpc-0.7.0/examples/olympicslocationagent_pb2_grpc.py +0 -125
  56. pydantic_rpc-0.7.0/tests/asyncechoservice.proto +0 -33
  57. pydantic_rpc-0.7.0/tests/echoservice.proto +0 -33
  58. pydantic_rpc-0.7.0/tests/google_protobuf/greeterwithduration.proto +0 -14
  59. pydantic_rpc-0.7.0/tests/google_protobuf/greeterwithtimestamp.proto +0 -14
  60. pydantic_rpc-0.7.0/tests/google_protobuf/test_google_protobuf.py +0 -41
  61. pydantic_rpc-0.7.0/tests/greeterwithduration.proto +0 -14
  62. pydantic_rpc-0.7.0/tests/greeterwithtimestamp.proto +0 -14
  63. pydantic_rpc-0.7.0/tests/test_apps.py +0 -378
  64. pydantic_rpc-0.7.0/tests/test_conversion.py +0 -1126
  65. pydantic_rpc-0.7.0/tests/test_mcp.py +0 -181
  66. pydantic_rpc-0.7.0/tests/test_utils.py +0 -511
  67. pydantic_rpc-0.7.0/uv.lock +0 -1548
  68. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/mcp/__init__.py +0 -0
  69. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/mcp/converter.py +0 -0
  70. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/mcp/exporter.py +0 -0
  71. {pydantic_rpc-0.7.0 → pydantic_rpc-0.9.0}/src/pydantic_rpc/py.typed +0 -0
@@ -1,20 +1,18 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.3
2
2
  Name: pydantic-rpc
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
5
5
  Author: Yasushi Itoh
6
- License-File: LICENSE
7
- Requires-Python: >=3.11
8
- Requires-Dist: annotated-types>=0.5.0
9
- Requires-Dist: connecpy==2.0.0
10
- Requires-Dist: grpcio-health-checking>=1.56.2
11
- Requires-Dist: grpcio-reflection>=1.56.2
12
- Requires-Dist: grpcio-tools>=1.56.2
6
+ Requires-Dist: annotated-types==0.7.0
7
+ Requires-Dist: pydantic>=2.1.1
13
8
  Requires-Dist: grpcio>=1.56.2
9
+ Requires-Dist: grpcio-tools>=1.56.2
10
+ Requires-Dist: grpcio-reflection>=1.56.2
11
+ Requires-Dist: grpcio-health-checking>=1.56.2
12
+ Requires-Dist: connecpy>=2.2.0
14
13
  Requires-Dist: mcp>=1.9.4
15
- Requires-Dist: pydantic>=2.1.1
16
- Requires-Dist: sonora>=0.2.3
17
14
  Requires-Dist: starlette>=0.27.0
15
+ Requires-Python: >=3.11
18
16
  Description-Content-Type: text/markdown
19
17
 
20
18
  # 🚀 PydanticRPC
@@ -77,7 +75,7 @@ import asyncio
77
75
  from openai import AsyncOpenAI
78
76
  from pydantic_ai import Agent
79
77
  from pydantic_ai.models.openai import OpenAIModel
80
- from pydantic_rpc import ConnecpyASGIApp, Message
78
+ from pydantic_rpc import ASGIApp, Message
81
79
 
82
80
 
83
81
  class CityLocation(Message):
@@ -108,7 +106,7 @@ class OlympicsLocationAgent:
108
106
  result = await self._agent.run(req.prompt())
109
107
  return result.data
110
108
 
111
- app = ConnecpyASGIApp()
109
+ app = ASGIApp()
112
110
  app.mount(OlympicsLocationAgent())
113
111
 
114
112
  ```
@@ -124,10 +122,10 @@ app.mount(OlympicsLocationAgent())
124
122
  - 💚 **Health Checking:** Built-in support for gRPC health checks using `grpc_health.v1`.
125
123
  - 🔎 **Server Reflection:** Built-in support for gRPC server reflection.
126
124
  - ⚡ **Asynchronous Support:** Easily create asynchronous gRPC services with `AsyncIOServer`.
127
- - **For gRPC-Web:**
128
- - 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
129
125
  - **For Connect-RPC:**
130
- - 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
126
+ - 🌐 **Full Protocol Support:** Native Connect-RPC support via `Connecpy` v2.2.0+
127
+ - 🔄 **All Streaming Patterns:** Unary, server streaming, client streaming, and bidirectional streaming
128
+ - 🌐 **WSGI/ASGI Applications:** Run as standard WSGI or ASGI applications for easy deployment
131
129
  - 🛠️ **Pre-generated Protobuf Files and Code:** Pre-generate proto files and corresponding code via the CLI. By setting the environment variable (PYDANTIC_RPC_SKIP_GENERATION), you can skip runtime generation.
132
130
  - 🤖 **MCP (Model Context Protocol) Support:** Expose your services as tools for AI assistants using the official MCP SDK, supporting both stdio and HTTP/SSE transports.
133
131
 
@@ -141,7 +139,11 @@ pip install pydantic-rpc
141
139
 
142
140
  ## 🚀 Getting Started
143
141
 
144
- ### 🔧 Synchronous Service Example
142
+ PydanticRPC supports two main protocols:
143
+ - **gRPC**: Traditional gRPC services with `Server` and `AsyncIOServer`
144
+ - **Connect-RPC**: Modern HTTP-based RPC with `ASGIApp` and `WSGIApp`
145
+
146
+ ### 🔧 Synchronous gRPC Service Example
145
147
 
146
148
  ```python
147
149
  from pydantic_rpc import Server, Message
@@ -162,7 +164,7 @@ if __name__ == "__main__":
162
164
  server.run(Greeter())
163
165
  ```
164
166
 
165
- ### ⚙️ Asynchronous Service Example
167
+ ### ⚙️ Asynchronous gRPC Service Example
166
168
 
167
169
  ```python
168
170
  import asyncio
@@ -195,7 +197,7 @@ if __name__ == "__main__":
195
197
 
196
198
  The AsyncIOServer automatically handles graceful shutdown on SIGTERM and SIGINT signals.
197
199
 
198
- ### 🌐 ASGI Application Example
200
+ ### 🌐 Connect-RPC ASGI Application Example
199
201
 
200
202
  ```python
201
203
  from pydantic_rpc import ASGIApp, Message
@@ -207,27 +209,17 @@ class HelloReply(Message):
207
209
  message: str
208
210
 
209
211
  class Greeter:
210
- def say_hello(self, request: HelloRequest) -> HelloReply:
212
+ async def say_hello(self, request: HelloRequest) -> HelloReply:
211
213
  return HelloReply(message=f"Hello, {request.name}!")
212
214
 
213
-
214
- async def app(scope, receive, send):
215
- """ASGI application.
216
-
217
- Args:
218
- scope (dict): The ASGI scope.
219
- receive (callable): The receive function.
220
- send (callable): The send function.
221
- """
222
- pass
223
-
224
- # Please note that `app` is any ASGI application, such as FastAPI or Starlette.
225
-
226
- app = ASGIApp(app)
215
+ app = ASGIApp()
227
216
  app.mount(Greeter())
217
+
218
+ # Run with uvicorn:
219
+ # uvicorn script:app --host 0.0.0.0 --port 8000
228
220
  ```
229
221
 
230
- ### 🌐 WSGI Application Example
222
+ ### 🌐 Connect-RPC WSGI Application Example
231
223
 
232
224
  ```python
233
225
  from pydantic_rpc import WSGIApp, Message
@@ -242,31 +234,69 @@ class Greeter:
242
234
  def say_hello(self, request: HelloRequest) -> HelloReply:
243
235
  return HelloReply(message=f"Hello, {request.name}!")
244
236
 
245
- def app(environ, start_response):
246
- """WSGI application.
247
-
248
- Args:
249
- environ (dict): The WSGI environment.
250
- start_response (callable): The start_response function.
251
- """
252
- pass
253
-
254
- # Please note that `app` is any WSGI application, such as Flask or Django.
255
-
256
- app = WSGIApp(app)
237
+ app = WSGIApp()
257
238
  app.mount(Greeter())
239
+
240
+ # Run with gunicorn:
241
+ # gunicorn script:app
258
242
  ```
259
243
 
260
- ### 🏆 Connecpy (Connect-RPC) Example
244
+ ### 🏆 Connect-RPC with Streaming Example
261
245
 
262
- PydanticRPC also partially supports Connect-RPC via connecpy. Check out “greeting_connecpy.py” for an example:
246
+ PydanticRPC provides native Connect-RPC support via Connecpy v2.2.0+, including full streaming capabilities and PEP 8 naming conventions. Check out our ASGI examples:
263
247
 
264
248
  ```bash
265
- uv run greeting_connecpy.py
249
+ # Run with uvicorn
250
+ uv run uvicorn greeting_asgi:app --port 3000
251
+
252
+ # Or run streaming example
253
+ uv run python examples/streaming_connecpy.py
266
254
  ```
267
255
 
268
256
  This will launch a Connecpy-based ASGI application that uses the same Pydantic models to serve Connect-RPC requests.
269
257
 
258
+ #### Streaming Support with Connecpy
259
+
260
+ Connecpy v2.2.0 provides full support for streaming RPCs with automatic PEP 8 naming (snake_case):
261
+
262
+ ```python
263
+ from typing import AsyncIterator
264
+ from pydantic_rpc import ASGIApp, Message
265
+
266
+ class StreamRequest(Message):
267
+ text: str
268
+ count: int
269
+
270
+ class StreamResponse(Message):
271
+ text: str
272
+ index: int
273
+
274
+ class StreamingService:
275
+ # Server streaming
276
+ async def server_stream(self, request: StreamRequest) -> AsyncIterator[StreamResponse]:
277
+ for i in range(request.count):
278
+ yield StreamResponse(text=f"{request.text}_{i}", index=i)
279
+
280
+ # Client streaming
281
+ async def client_stream(self, requests: AsyncIterator[StreamRequest]) -> StreamResponse:
282
+ texts = []
283
+ async for req in requests:
284
+ texts.append(req.text)
285
+ return StreamResponse(text=" ".join(texts), index=len(texts))
286
+
287
+ # Bidirectional streaming
288
+ async def bidi_stream(
289
+ self, requests: AsyncIterator[StreamRequest]
290
+ ) -> AsyncIterator[StreamResponse]:
291
+ idx = 0
292
+ async for req in requests:
293
+ yield StreamResponse(text=f"Echo: {req.text}", index=idx)
294
+ idx += 1
295
+
296
+ app = ASGIApp()
297
+ app.mount(StreamingService())
298
+ ```
299
+
270
300
  > [!NOTE]
271
301
  > Please install `protoc-gen-connecpy` to run the Connecpy example.
272
302
  >
@@ -303,9 +333,9 @@ export PYDANTIC_RPC_RESERVED_FIELDS=1
303
333
 
304
334
  ## 💎 Advanced Features
305
335
 
306
- ### 🌊 Response Streaming
307
- PydanticRPC supports streaming responses only for asynchronous gRPC and gRPC-Web services.
308
- If a service class methods return type is `typing.AsyncIterator[T]`, the method is considered a streaming method.
336
+ ### 🌊 Response Streaming (gRPC)
337
+ PydanticRPC supports streaming responses for both gRPC and Connect-RPC services.
338
+ If a service class method's return type is `typing.AsyncIterator[T]`, the method is considered a streaming method.
309
339
 
310
340
 
311
341
  Please see the sample code below:
@@ -674,9 +704,207 @@ buf: * (#2) Call complete
674
704
  %
675
705
  ```
676
706
 
707
+ ### 🪶 Empty Messages
708
+
709
+ Empty request/response messages are automatically mapped to `google.protobuf.Empty`:
710
+
711
+ ```python
712
+ from pydantic_rpc import AsyncIOServer, Message
713
+
714
+
715
+ class EmptyRequest(Message):
716
+ pass # Automatically uses google.protobuf.Empty
717
+
718
+
719
+ class GreetingResponse(Message):
720
+ message: str
721
+
722
+
723
+ class GreetingService:
724
+ async def say_hello(self, request: EmptyRequest) -> GreetingResponse:
725
+ return GreetingResponse(message="Hello!")
726
+
727
+ async def get_default_greeting(self) -> GreetingResponse:
728
+ # Method with no request parameter (implicitly empty)
729
+ return GreetingResponse(message="Hello, World!")
730
+ ```
731
+
732
+ ### 🎨 Custom Serialization
733
+
734
+ Pydantic's serialization decorators are fully supported:
735
+
736
+ ```python
737
+ from typing import Any
738
+ from pydantic import field_serializer, model_serializer
739
+ from pydantic_rpc import Message
740
+
741
+
742
+ class UserMessage(Message):
743
+ name: str
744
+ age: int
745
+
746
+ @field_serializer('name')
747
+ def serialize_name(self, name: str) -> str:
748
+ """Always uppercase the name when serializing."""
749
+ return name.upper()
750
+
751
+
752
+ class ComplexMessage(Message):
753
+ value: int
754
+ multiplier: int
755
+
756
+ @model_serializer
757
+ def serialize_model(self) -> dict[str, Any]:
758
+ """Custom serialization with computed fields."""
759
+ return {
760
+ 'value': self.value,
761
+ 'multiplier': self.multiplier,
762
+ 'result': self.value * self.multiplier # Computed field
763
+ }
764
+ ```
765
+
766
+ The serializers are automatically applied when converting between Pydantic models and protobuf messages.
767
+
768
+ #### ⚠️ Limitations and Considerations
769
+
770
+ **1. Nested Message serializers are now supported (v0.8.0+)**
771
+ ```python
772
+ class Address(Message):
773
+ city: str
774
+
775
+ @field_serializer("city")
776
+ def serialize_city(self, city: str) -> str:
777
+ return city.upper()
778
+
779
+ class User(Message):
780
+ name: str
781
+ address: Address # ← Address's serializers ARE applied with DEEP strategy
782
+
783
+ @field_serializer("name")
784
+ def serialize_name(self, name: str) -> str:
785
+ return name.upper() # ← This IS applied
786
+ ```
787
+
788
+ **Serializer Strategy Control:**
789
+ You can control how nested serializers are applied via environment variable:
790
+ ```bash
791
+ # Apply serializers at all nesting levels (default)
792
+ export PYDANTIC_RPC_SERIALIZER_STRATEGY=deep
793
+
794
+ # Apply only top-level serializers
795
+ export PYDANTIC_RPC_SERIALIZER_STRATEGY=shallow
796
+
797
+ # Disable all serializers
798
+ export PYDANTIC_RPC_SERIALIZER_STRATEGY=none
799
+ ```
800
+
801
+ **Performance Impact:**
802
+ - DEEP strategy: ~4% overhead for simple nested structures
803
+ - SHALLOW strategy: ~2% overhead (only top-level)
804
+ - NONE strategy: No overhead (serializers disabled)
805
+
806
+ **2. New fields added by serializers are ignored**
807
+ ```python
808
+ class ComplexMessage(Message):
809
+ value: int
810
+ multiplier: int
811
+
812
+ @model_serializer
813
+ def serialize_model(self) -> dict[str, Any]:
814
+ return {
815
+ "value": self.value,
816
+ "multiplier": self.multiplier,
817
+ "result": self.value * self.multiplier # ← Won't appear in protobuf
818
+ }
819
+ ```
820
+ **Problem**: The `result` field doesn't exist in the Message definition, so it's not in the protobuf schema.
821
+
822
+ **3. Type must remain consistent**
823
+ ```python
824
+ class BadExample(Message):
825
+ number: int
826
+
827
+ @field_serializer("number")
828
+ def serialize_number(self, number: int) -> str: # ❌ int → str
829
+ return str(number) # This will cause issues
830
+ ```
831
+
832
+ **4. Union/Optional fields have limited support**
833
+ ```python
834
+ class UnionExample(Message):
835
+ data: str | int | None # Union type
836
+
837
+ @field_serializer("data")
838
+ def serialize_data(self, data: str | int | None) -> str | int | None:
839
+ # Serializer may not be applied to Union types
840
+ return data
841
+ ```
842
+
843
+ **5. Errors fail silently with fallback**
844
+ ```python
845
+ class RiskyMessage(Message):
846
+ value: int
847
+
848
+ @field_serializer("value")
849
+ def serialize_value(self, value: int) -> int:
850
+ if value == 0:
851
+ raise ValueError("Cannot serialize zero")
852
+ return value * 2
853
+
854
+ # If error occurs, original value is used (silent fallback)
855
+ ```
856
+
857
+ **6. Circular references are handled gracefully**
858
+ ```python
859
+ class Node(Message):
860
+ value: str
861
+ child: "Node | None" = None
862
+
863
+ @field_serializer("value")
864
+ def serialize_value(self, v: str) -> str:
865
+ return v.upper()
866
+
867
+ # Circular references are detected and prevented
868
+ node1 = Node(value="first")
869
+ node2 = Node(value="second")
870
+ node1.child = node2
871
+ node2.child = node1 # Circular reference
872
+
873
+ # When converting to protobuf:
874
+ # - Circular references are detected
875
+ # - Empty proto is returned for repeated objects
876
+ # - No infinite recursion occurs
877
+ # Note: Pydantic's model_dump() will fail on circular refs,
878
+ # so serializers won't be applied in this case
879
+ ```
880
+
881
+ **✅ Recommended Usage:**
882
+ ```python
883
+ class GoodMessage(Message):
884
+ # Use with primitive types
885
+ name: str
886
+ age: int
887
+
888
+ @field_serializer("name")
889
+ def normalize_name(self, name: str) -> str:
890
+ return name.strip().title() # Normalization
891
+
892
+ @field_serializer("age")
893
+ def clamp_age(self, age: int) -> int:
894
+ return max(0, min(age, 150)) # Range limiting
895
+ ```
896
+
897
+ **Best Practices:**
898
+ - Use serializers primarily for primitive types (str, int, float, bool)
899
+ - Keep type consistency (int → int, str → str)
900
+ - Avoid complex transformations or side effects
901
+ - Test error cases thoroughly
902
+ - Be aware that errors fail silently
903
+
677
904
  ### 🔗 Multiple Services with Custom Interceptors
905
+ >>>>>>> origin/main
678
906
 
679
- PydanticRPC supports defining and running multiple services in a single server:
907
+ PydanticRPC supports defining and running multiple gRPC services in a single server:
680
908
 
681
909
  ```python
682
910
  from datetime import datetime
@@ -807,11 +1035,11 @@ Any MCP-compatible client can connect to your service. For example, to configure
807
1035
  MCP can also be mounted to existing ASGI applications:
808
1036
 
809
1037
  ```python
810
- from pydantic_rpc import ConnecpyASGIApp
1038
+ from pydantic_rpc import ASGIApp
811
1039
  from pydantic_rpc.mcp import MCPExporter
812
1040
 
813
1041
  # Create Connect-RPC ASGI app
814
- app = ConnecpyASGIApp()
1042
+ app = ASGIApp()
815
1043
  app.mount(MathService())
816
1044
 
817
1045
  # Add MCP support via HTTP/SSE
@@ -905,8 +1133,8 @@ This approach works because protobuf allows message types within `oneof` fields,
905
1133
  - [x] unary-stream
906
1134
  - [x] stream-unary
907
1135
  - [x] stream-stream
908
- - [ ] Betterproto Support
909
- - [ ] Sonora-connect Support
1136
+ - [x] Empty Message Support (automatic google.protobuf.Empty)
1137
+ - [x] Pydantic Serializer Support (@model_serializer, @field_serializer)
910
1138
  - [ ] Custom Health Check Support
911
1139
  - [x] MCP (Model Context Protocol) Support via official MCP SDK
912
1140
  - [ ] Add more examples