pydantic-rpc 0.6.1__tar.gz → 0.7.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 (81) hide show
  1. pydantic_rpc-0.7.0/.github/workflows/release.yml +35 -0
  2. pydantic_rpc-0.7.0/.github/workflows/test.yml +47 -0
  3. pydantic_rpc-0.7.0/.gitignore +24 -0
  4. pydantic_rpc-0.6.1/README.md → pydantic_rpc-0.7.0/PKG-INFO +162 -8
  5. pydantic_rpc-0.6.1/PKG-INFO → pydantic_rpc-0.7.0/README.md +143 -23
  6. pydantic_rpc-0.7.0/docs/mcp.md +95 -0
  7. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/asyncio_greeting.py +22 -22
  8. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/barservice.proto +17 -17
  9. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/foobar.py +76 -76
  10. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/foobar_client.py +20 -20
  11. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/fooservice.proto +21 -21
  12. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/google/protobuf/duration.proto +115 -115
  13. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/google/protobuf/timestamp.proto +144 -144
  14. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter.proto +35 -35
  15. pydantic_rpc-0.7.0/examples/greeter_connecpy.py +124 -0
  16. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_connecpy_client.py +10 -9
  17. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_pb2_grpc.py +4 -17
  18. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeting.py +45 -45
  19. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeting_asgi.py +55 -55
  20. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeting_wsgi.py +63 -63
  21. pydantic_rpc-0.7.0/examples/mcp_debug_example.py +74 -0
  22. pydantic_rpc-0.7.0/examples/mcp_example.py +129 -0
  23. pydantic_rpc-0.7.0/examples/mcp_http_example.py +125 -0
  24. pydantic_rpc-0.7.0/examples/mcp_simple_calculator.py +45 -0
  25. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicsagent.proto +40 -40
  26. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicslocationagent.proto +24 -24
  27. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/pyproject.toml +8 -2
  28. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/src/pydantic_rpc/__init__.py +10 -0
  29. pydantic_rpc-0.7.0/src/pydantic_rpc/core.py +2445 -0
  30. pydantic_rpc-0.7.0/src/pydantic_rpc/mcp/__init__.py +5 -0
  31. pydantic_rpc-0.7.0/src/pydantic_rpc/mcp/converter.py +115 -0
  32. pydantic_rpc-0.7.0/src/pydantic_rpc/mcp/exporter.py +283 -0
  33. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/tests/asyncechoservice.proto +33 -33
  34. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/tests/echoservice.proto +33 -33
  35. pydantic_rpc-0.7.0/tests/google_protobuf/greeterwithduration.proto +14 -0
  36. pydantic_rpc-0.7.0/tests/google_protobuf/greeterwithtimestamp.proto +14 -0
  37. pydantic_rpc-0.7.0/tests/google_protobuf/test_google_protobuf.py +41 -0
  38. pydantic_rpc-0.7.0/tests/greeterwithduration.proto +14 -0
  39. pydantic_rpc-0.7.0/tests/greeterwithtimestamp.proto +14 -0
  40. pydantic_rpc-0.7.0/tests/test_apps.py +378 -0
  41. pydantic_rpc-0.7.0/tests/test_conversion.py +1126 -0
  42. pydantic_rpc-0.7.0/tests/test_mcp.py +181 -0
  43. pydantic_rpc-0.7.0/tests/test_utils.py +511 -0
  44. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/uv.lock +758 -846
  45. pydantic_rpc-0.6.1/.gitignore +0 -13
  46. pydantic_rpc-0.6.1/examples/greeter_connecpy.py +0 -111
  47. pydantic_rpc-0.6.1/src/pydantic_rpc/core.py +0 -1455
  48. pydantic_rpc-0.6.1/tests/asyncechoservice_connecpy.py +0 -109
  49. pydantic_rpc-0.6.1/tests/asyncechoservice_pb2.py +0 -37
  50. pydantic_rpc-0.6.1/tests/asyncechoservice_pb2.pyi +0 -17
  51. pydantic_rpc-0.6.1/tests/asyncechoservice_pb2_grpc.py +0 -121
  52. pydantic_rpc-0.6.1/tests/echoservice_connecpy.py +0 -109
  53. pydantic_rpc-0.6.1/tests/echoservice_pb2.py +0 -37
  54. pydantic_rpc-0.6.1/tests/echoservice_pb2.pyi +0 -17
  55. pydantic_rpc-0.6.1/tests/echoservice_pb2_grpc.py +0 -119
  56. pydantic_rpc-0.6.1/tests/test_apps.py +0 -190
  57. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/.python-version +0 -0
  58. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/LICENSE +0 -0
  59. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/README.md +0 -0
  60. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/agent_aio_grpc.py +0 -0
  61. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/agent_connecpy.py +0 -0
  62. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/barservice_pb2.py +0 -0
  63. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/barservice_pb2.pyi +0 -0
  64. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/barservice_pb2_grpc.py +0 -0
  65. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/fooservice_pb2.py +0 -0
  66. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/fooservice_pb2.pyi +0 -0
  67. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/fooservice_pb2_grpc.py +0 -0
  68. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_client.py +0 -0
  69. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_pb2.py +0 -0
  70. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_pb2.pyi +0 -0
  71. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeter_sonora_client.py +0 -0
  72. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeting_connecpy.py +0 -0
  73. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/greeting_using_exsiting_pb2_modules.py +0 -0
  74. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicsagent_pb2.py +0 -0
  75. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicsagent_pb2.pyi +0 -0
  76. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicsagent_pb2_grpc.py +0 -0
  77. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicslocationagent_connecpy.py +0 -0
  78. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicslocationagent_pb2.py +0 -0
  79. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicslocationagent_pb2.pyi +0 -0
  80. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/examples/olympicslocationagent_pb2_grpc.py +0 -0
  81. {pydantic_rpc-0.6.1 → pydantic_rpc-0.7.0}/src/pydantic_rpc/py.typed +0 -0
@@ -0,0 +1,35 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ tags:
5
+ - "v*"
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ id-token: write
10
+ attestations: write
11
+ contents: write
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-24.04
16
+ steps:
17
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
18
+
19
+ - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
20
+
21
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
22
+ with:
23
+ python-version-file: "pyproject.toml"
24
+
25
+ - run: uv sync --frozen
26
+
27
+ - run: uv build
28
+
29
+ - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
30
+ with:
31
+ password: ${{ secrets.PYPI_TOKEN }}
32
+
33
+ - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
34
+ with:
35
+ subject-path: dist/*.tar.gz
@@ -0,0 +1,47 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ main ]
6
+ push:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ fail-fast: false
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Go
22
+ uses: actions/setup-go@v5
23
+ with:
24
+ go-version: '1.24'
25
+ cache: true
26
+
27
+ - name: Install protoc-gen-connecpy plugin
28
+ run: go install github.com/i2y/connecpy/v2/protoc-gen-connecpy@latest
29
+
30
+ - name: Setup uv with cache
31
+ id: setup-uv
32
+ uses: astral-sh/setup-uv@v6
33
+ with:
34
+ enable-cache: true
35
+
36
+ - name: Install Python ${{ matrix.python-version }}
37
+ run: uv python install ${{ matrix.python-version }}
38
+
39
+ - name: Install dependencies
40
+ run: uv sync
41
+
42
+ - name: Run tests
43
+ run: |
44
+ export PATH="$PATH:$(go env GOPATH)/bin"
45
+ uv run pytest
46
+ env:
47
+ PYTHONPATH: ${{ github.workspace }}/src
@@ -0,0 +1,24 @@
1
+ # python generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # venv
10
+ .venv
11
+
12
+ # vscode
13
+ .vscode
14
+
15
+ # development
16
+ .devcontainer/
17
+ .env
18
+ rpc_files/
19
+
20
+ # tests
21
+ tests/**/*.py
22
+ tests/**/*.pyi
23
+ !tests/**/test_*.py
24
+ .coverage
@@ -1,3 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydantic-rpc
3
+ Version: 0.7.0
4
+ Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
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
13
+ Requires-Dist: grpcio>=1.56.2
14
+ Requires-Dist: mcp>=1.9.4
15
+ Requires-Dist: pydantic>=2.1.1
16
+ Requires-Dist: sonora>=0.2.3
17
+ Requires-Dist: starlette>=0.27.0
18
+ Description-Content-Type: text/markdown
19
+
1
20
  # 🚀 PydanticRPC
2
21
 
3
22
  **PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[Connect RPC](https://connectrpc.com/docs/protocol/) services without writing any protobuf files. Instead, it automatically generates protobuf files on the fly from the method signatures of your Python objects and the type signatures of your Pydantic models.
@@ -110,6 +129,7 @@ app.mount(OlympicsLocationAgent())
110
129
  - **For Connect-RPC:**
111
130
  - 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
112
131
  - 🛠️ **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
+ - 🤖 **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.
113
133
 
114
134
  ## 📦 Installation
115
135
 
@@ -163,12 +183,18 @@ class Greeter:
163
183
  return HelloReply(message=f"Hello, {request.name}!")
164
184
 
165
185
 
186
+ async def main():
187
+ # You can specify a custom port (default is 50051)
188
+ server = AsyncIOServer(port=50052)
189
+ await server.run(Greeter())
190
+
191
+
166
192
  if __name__ == "__main__":
167
- server = AsyncIOServer()
168
- loop = asyncio.get_event_loop()
169
- loop.run_until_complete(server.run(Greeter()))
193
+ asyncio.run(main())
170
194
  ```
171
195
 
196
+ The AsyncIOServer automatically handles graceful shutdown on SIGTERM and SIGINT signals.
197
+
172
198
  ### 🌐 ASGI Application Example
173
199
 
174
200
  ```python
@@ -248,7 +274,7 @@ This will launch a Connecpy-based ASGI application that uses the same Pydantic m
248
274
  > - Please follow the instruction described in https://go.dev/doc/install.
249
275
  > 2. Install `protoc-gen-connecpy`:
250
276
  > ```bash
251
- > go install github.com/connecpy/protoc-gen-connecpy@latest
277
+ > go install github.com/i2y/connecpy/v2/protoc-gen-connecpy@latest
252
278
  > ```
253
279
 
254
280
  ## ♻️ Skipping Protobuf Generation
@@ -260,6 +286,21 @@ export PYDANTIC_RPC_SKIP_GENERATION=true
260
286
 
261
287
  When this variable is set to "true", PydanticRPC will load existing pre-generated modules rather than generating them on the fly.
262
288
 
289
+ ## 🪧 Setting Protobuf and Connecpy/gRPC generation directory
290
+ By default your files will be generated in the current working directory where you ran the code from, but you can set a custom specific directory by setting the environment variable below:
291
+
292
+ ```bash
293
+ export PYDANTIC_RPC_PROTO_PATH=/your/path
294
+ ```
295
+
296
+ ## ⚠️ Reserved Fields
297
+
298
+ You can also set an environment variable to reserve a set number of fields for proto generation, for backward and forward compatibility.
299
+
300
+ ```bash
301
+ export PYDANTIC_RPC_RESERVED_FIELDS=1
302
+ ```
303
+
263
304
  ## 💎 Advanced Features
264
305
 
265
306
  ### 🌊 Response Streaming
@@ -718,6 +759,74 @@ if __name__ == "__main__":
718
759
 
719
760
  TODO
720
761
 
762
+ ### 🤖 MCP (Model Context Protocol) Support
763
+
764
+ PydanticRPC can expose your services as MCP tools for AI assistants using FastMCP. This enables seamless integration with any MCP-compatible client.
765
+
766
+ #### Stdio Mode Example
767
+
768
+ ```python
769
+ from pydantic_rpc import Message
770
+ from pydantic_rpc.mcp import MCPExporter
771
+
772
+ class CalculateRequest(Message):
773
+ expression: str
774
+
775
+ class CalculateResponse(Message):
776
+ result: float
777
+
778
+ class MathService:
779
+ def calculate(self, req: CalculateRequest) -> CalculateResponse:
780
+ result = eval(req.expression, {"__builtins__": {}}, {})
781
+ return CalculateResponse(result=float(result))
782
+
783
+ # Run as MCP stdio server
784
+ if __name__ == "__main__":
785
+ service = MathService()
786
+ mcp = MCPExporter(service)
787
+ mcp.run_stdio()
788
+ ```
789
+
790
+ #### Configuring MCP Clients
791
+
792
+ Any MCP-compatible client can connect to your service. For example, to configure Claude Desktop:
793
+
794
+ ```json
795
+ {
796
+ "mcpServers": {
797
+ "my-math-service": {
798
+ "command": "python",
799
+ "args": ["/path/to/math_mcp_server.py"]
800
+ }
801
+ }
802
+ }
803
+ ```
804
+
805
+ #### HTTP/ASGI Mode Example
806
+
807
+ MCP can also be mounted to existing ASGI applications:
808
+
809
+ ```python
810
+ from pydantic_rpc import ConnecpyASGIApp
811
+ from pydantic_rpc.mcp import MCPExporter
812
+
813
+ # Create Connect-RPC ASGI app
814
+ app = ConnecpyASGIApp()
815
+ app.mount(MathService())
816
+
817
+ # Add MCP support via HTTP/SSE
818
+ mcp = MCPExporter(MathService())
819
+ mcp.mount_to_asgi(app, path="/mcp")
820
+
821
+ # Run with uvicorn
822
+ import uvicorn
823
+ uvicorn.run(app, host="127.0.0.1", port=8000)
824
+ ```
825
+
826
+ MCP endpoints will be available at:
827
+ - SSE: `GET http://localhost:8000/mcp/sse`
828
+ - Messages: `POST http://localhost:8000/mcp/messages/`
829
+
721
830
  ### 🗄️ Protobuf file and code (Python files) generation using CLI
722
831
 
723
832
  You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
@@ -747,16 +856,61 @@ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you coul
747
856
  | subclass of pydantic.BaseModel | message |
748
857
 
749
858
 
859
+ ## ⚠️ Known Limitations
860
+
861
+ ### Union Types with Collections
862
+
863
+ Due to protobuf's `oneof` restrictions, you cannot use `Union` types that contain `repeated` (list/tuple) or `map` (dict) fields directly. This is a limitation of the protobuf specification itself.
864
+
865
+ **❌ Not Supported:**
866
+ ```python
867
+ from typing import Union, List, Dict
868
+ from pydantic_rpc import Message
869
+
870
+ # These will fail during proto compilation
871
+ class MyMessage(Message):
872
+ # Union with list - NOT SUPPORTED
873
+ field1: Union[List[int], str]
874
+
875
+ # Union with dict - NOT SUPPORTED
876
+ field2: Union[Dict[str, int], int]
877
+
878
+ # Union with nested collections - NOT SUPPORTED
879
+ field3: Union[List[Dict[str, int]], str]
880
+ ```
881
+
882
+ **✅ Workaround - Use Message Wrappers:**
883
+ ```python
884
+ from typing import Union, List, Dict
885
+ from pydantic_rpc import Message
886
+
887
+ # Wrap collections in Message types
888
+ class IntList(Message):
889
+ values: List[int]
890
+
891
+ class StringIntMap(Message):
892
+ values: Dict[str, int]
893
+
894
+ class MyMessage(Message):
895
+ # Now these work!
896
+ field1: Union[IntList, str]
897
+ field2: Union[StringIntMap, int]
898
+ ```
899
+
900
+ This approach works because protobuf allows message types within `oneof` fields, and the collections are contained within those messages.
901
+
902
+
750
903
  ## TODO
751
- - [ ] Streaming Support
904
+ - [x] Streaming Support
752
905
  - [x] unary-stream
753
- - [ ] stream-unary
754
- - [ ] stream-stream
906
+ - [x] stream-unary
907
+ - [x] stream-stream
755
908
  - [ ] Betterproto Support
756
909
  - [ ] Sonora-connect Support
757
910
  - [ ] Custom Health Check Support
911
+ - [x] MCP (Model Context Protocol) Support via official MCP SDK
758
912
  - [ ] Add more examples
759
- - [ ] Add tests
913
+ - [x] Add tests
760
914
 
761
915
  ## 📜 License
762
916
 
@@ -1,18 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: pydantic-rpc
3
- Version: 0.6.1
4
- Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
5
- Author: Yasushi Itoh
6
- License-File: LICENSE
7
- Requires-Python: >=3.11
8
- Requires-Dist: connecpy>=1.4.1
9
- Requires-Dist: grpcio-health-checking>=1.56.2
10
- Requires-Dist: grpcio-reflection>=1.56.2
11
- Requires-Dist: grpcio-tools>=1.56.2
12
- Requires-Dist: pydantic>=2.1.1
13
- Requires-Dist: sonora>=0.2.3
14
- Description-Content-Type: text/markdown
15
-
16
1
  # 🚀 PydanticRPC
17
2
 
18
3
  **PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[Connect RPC](https://connectrpc.com/docs/protocol/) services without writing any protobuf files. Instead, it automatically generates protobuf files on the fly from the method signatures of your Python objects and the type signatures of your Pydantic models.
@@ -125,6 +110,7 @@ app.mount(OlympicsLocationAgent())
125
110
  - **For Connect-RPC:**
126
111
  - 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
127
112
  - 🛠️ **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.
113
+ - 🤖 **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.
128
114
 
129
115
  ## 📦 Installation
130
116
 
@@ -178,12 +164,18 @@ class Greeter:
178
164
  return HelloReply(message=f"Hello, {request.name}!")
179
165
 
180
166
 
167
+ async def main():
168
+ # You can specify a custom port (default is 50051)
169
+ server = AsyncIOServer(port=50052)
170
+ await server.run(Greeter())
171
+
172
+
181
173
  if __name__ == "__main__":
182
- server = AsyncIOServer()
183
- loop = asyncio.get_event_loop()
184
- loop.run_until_complete(server.run(Greeter()))
174
+ asyncio.run(main())
185
175
  ```
186
176
 
177
+ The AsyncIOServer automatically handles graceful shutdown on SIGTERM and SIGINT signals.
178
+
187
179
  ### 🌐 ASGI Application Example
188
180
 
189
181
  ```python
@@ -263,7 +255,7 @@ This will launch a Connecpy-based ASGI application that uses the same Pydantic m
263
255
  > - Please follow the instruction described in https://go.dev/doc/install.
264
256
  > 2. Install `protoc-gen-connecpy`:
265
257
  > ```bash
266
- > go install github.com/connecpy/protoc-gen-connecpy@latest
258
+ > go install github.com/i2y/connecpy/v2/protoc-gen-connecpy@latest
267
259
  > ```
268
260
 
269
261
  ## ♻️ Skipping Protobuf Generation
@@ -275,6 +267,21 @@ export PYDANTIC_RPC_SKIP_GENERATION=true
275
267
 
276
268
  When this variable is set to "true", PydanticRPC will load existing pre-generated modules rather than generating them on the fly.
277
269
 
270
+ ## 🪧 Setting Protobuf and Connecpy/gRPC generation directory
271
+ By default your files will be generated in the current working directory where you ran the code from, but you can set a custom specific directory by setting the environment variable below:
272
+
273
+ ```bash
274
+ export PYDANTIC_RPC_PROTO_PATH=/your/path
275
+ ```
276
+
277
+ ## ⚠️ Reserved Fields
278
+
279
+ You can also set an environment variable to reserve a set number of fields for proto generation, for backward and forward compatibility.
280
+
281
+ ```bash
282
+ export PYDANTIC_RPC_RESERVED_FIELDS=1
283
+ ```
284
+
278
285
  ## 💎 Advanced Features
279
286
 
280
287
  ### 🌊 Response Streaming
@@ -733,6 +740,74 @@ if __name__ == "__main__":
733
740
 
734
741
  TODO
735
742
 
743
+ ### 🤖 MCP (Model Context Protocol) Support
744
+
745
+ PydanticRPC can expose your services as MCP tools for AI assistants using FastMCP. This enables seamless integration with any MCP-compatible client.
746
+
747
+ #### Stdio Mode Example
748
+
749
+ ```python
750
+ from pydantic_rpc import Message
751
+ from pydantic_rpc.mcp import MCPExporter
752
+
753
+ class CalculateRequest(Message):
754
+ expression: str
755
+
756
+ class CalculateResponse(Message):
757
+ result: float
758
+
759
+ class MathService:
760
+ def calculate(self, req: CalculateRequest) -> CalculateResponse:
761
+ result = eval(req.expression, {"__builtins__": {}}, {})
762
+ return CalculateResponse(result=float(result))
763
+
764
+ # Run as MCP stdio server
765
+ if __name__ == "__main__":
766
+ service = MathService()
767
+ mcp = MCPExporter(service)
768
+ mcp.run_stdio()
769
+ ```
770
+
771
+ #### Configuring MCP Clients
772
+
773
+ Any MCP-compatible client can connect to your service. For example, to configure Claude Desktop:
774
+
775
+ ```json
776
+ {
777
+ "mcpServers": {
778
+ "my-math-service": {
779
+ "command": "python",
780
+ "args": ["/path/to/math_mcp_server.py"]
781
+ }
782
+ }
783
+ }
784
+ ```
785
+
786
+ #### HTTP/ASGI Mode Example
787
+
788
+ MCP can also be mounted to existing ASGI applications:
789
+
790
+ ```python
791
+ from pydantic_rpc import ConnecpyASGIApp
792
+ from pydantic_rpc.mcp import MCPExporter
793
+
794
+ # Create Connect-RPC ASGI app
795
+ app = ConnecpyASGIApp()
796
+ app.mount(MathService())
797
+
798
+ # Add MCP support via HTTP/SSE
799
+ mcp = MCPExporter(MathService())
800
+ mcp.mount_to_asgi(app, path="/mcp")
801
+
802
+ # Run with uvicorn
803
+ import uvicorn
804
+ uvicorn.run(app, host="127.0.0.1", port=8000)
805
+ ```
806
+
807
+ MCP endpoints will be available at:
808
+ - SSE: `GET http://localhost:8000/mcp/sse`
809
+ - Messages: `POST http://localhost:8000/mcp/messages/`
810
+
736
811
  ### 🗄️ Protobuf file and code (Python files) generation using CLI
737
812
 
738
813
  You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
@@ -762,16 +837,61 @@ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you coul
762
837
  | subclass of pydantic.BaseModel | message |
763
838
 
764
839
 
840
+ ## ⚠️ Known Limitations
841
+
842
+ ### Union Types with Collections
843
+
844
+ Due to protobuf's `oneof` restrictions, you cannot use `Union` types that contain `repeated` (list/tuple) or `map` (dict) fields directly. This is a limitation of the protobuf specification itself.
845
+
846
+ **❌ Not Supported:**
847
+ ```python
848
+ from typing import Union, List, Dict
849
+ from pydantic_rpc import Message
850
+
851
+ # These will fail during proto compilation
852
+ class MyMessage(Message):
853
+ # Union with list - NOT SUPPORTED
854
+ field1: Union[List[int], str]
855
+
856
+ # Union with dict - NOT SUPPORTED
857
+ field2: Union[Dict[str, int], int]
858
+
859
+ # Union with nested collections - NOT SUPPORTED
860
+ field3: Union[List[Dict[str, int]], str]
861
+ ```
862
+
863
+ **✅ Workaround - Use Message Wrappers:**
864
+ ```python
865
+ from typing import Union, List, Dict
866
+ from pydantic_rpc import Message
867
+
868
+ # Wrap collections in Message types
869
+ class IntList(Message):
870
+ values: List[int]
871
+
872
+ class StringIntMap(Message):
873
+ values: Dict[str, int]
874
+
875
+ class MyMessage(Message):
876
+ # Now these work!
877
+ field1: Union[IntList, str]
878
+ field2: Union[StringIntMap, int]
879
+ ```
880
+
881
+ This approach works because protobuf allows message types within `oneof` fields, and the collections are contained within those messages.
882
+
883
+
765
884
  ## TODO
766
- - [ ] Streaming Support
885
+ - [x] Streaming Support
767
886
  - [x] unary-stream
768
- - [ ] stream-unary
769
- - [ ] stream-stream
887
+ - [x] stream-unary
888
+ - [x] stream-stream
770
889
  - [ ] Betterproto Support
771
890
  - [ ] Sonora-connect Support
772
891
  - [ ] Custom Health Check Support
892
+ - [x] MCP (Model Context Protocol) Support via official MCP SDK
773
893
  - [ ] Add more examples
774
- - [ ] Add tests
894
+ - [x] Add tests
775
895
 
776
896
  ## 📜 License
777
897
 
@@ -0,0 +1,95 @@
1
+ # MCP (Model Context Protocol) Support
2
+
3
+ pydantic-rpc provides built-in support for exposing your services as MCP servers, allowing them to be used as tools by AI assistants.
4
+
5
+ ## Features
6
+
7
+ - **Automatic tool generation** from pydantic-rpc service methods
8
+ - **Type-safe** parameter and return value handling using Pydantic models
9
+ - **Multiple transport options**:
10
+ - stdio (for Claude Desktop and other MCP clients)
11
+ - HTTP/SSE (for web-based integrations)
12
+ - **Async and sync** method support
13
+
14
+ ## Quick Start
15
+
16
+ ### 1. Define your service
17
+
18
+ ```python
19
+ from pydantic_rpc import Message
20
+ from pydantic_rpc.mcp import MCPExporter
21
+
22
+ class CalculateRequest(Message):
23
+ expression: str
24
+
25
+ class CalculateResponse(Message):
26
+ result: float
27
+
28
+ class CalculatorService:
29
+ def calculate(self, request: CalculateRequest) -> CalculateResponse:
30
+ result = eval(request.expression, {"__builtins__": {}}, {})
31
+ return CalculateResponse(result=float(result))
32
+ ```
33
+
34
+ ### 2. Export as MCP server
35
+
36
+ ```python
37
+ if __name__ == "__main__":
38
+ service = CalculatorService()
39
+ mcp = MCPExporter(service, name="Calculator")
40
+ mcp.run_stdio() # For stdio transport
41
+ ```
42
+
43
+ ### 3. Use with MCP clients
44
+
45
+ Configure your MCP client to run the Python script. The service methods will be automatically exposed as tools.
46
+
47
+ ## Transport Options
48
+
49
+ ### stdio Transport
50
+
51
+ For use with Claude Desktop and other stdio-based MCP clients:
52
+
53
+ ```python
54
+ mcp = MCPExporter(service)
55
+ mcp.run_stdio()
56
+ ```
57
+
58
+ ### HTTP/SSE Transport
59
+
60
+ For web-based integrations:
61
+
62
+ ```python
63
+ # Standalone HTTP server
64
+ app = mcp.get_asgi_app()
65
+ # Run with uvicorn or similar ASGI server
66
+
67
+ # Mount to existing ASGI app
68
+ from pydantic_rpc import ConnecpyASGIApp
69
+ connect_app = ConnecpyASGIApp()
70
+ connect_app.mount(service)
71
+ mcp.mount_to_asgi(connect_app, path="/mcp")
72
+ ```
73
+
74
+ ## Examples
75
+
76
+ See the `examples/` directory for complete examples:
77
+ - `mcp_example.py` - Calculator service with stdio transport
78
+ - `mcp_http_example.py` - HTTP/SSE transport with Connect-RPC integration
79
+ - `mcp_simple_calculator.py` - Minimal example
80
+
81
+ ## How It Works
82
+
83
+ 1. MCPExporter introspects your service class to find public methods
84
+ 2. For each method, it extracts parameter and return type information
85
+ 3. Pydantic models are converted to JSON Schema for tool descriptions
86
+ 4. Methods are wrapped to handle MCP protocol messages
87
+ 5. The resulting tools can be called by MCP clients
88
+
89
+ ## Requirements
90
+
91
+ ```bash
92
+ pip install pydantic-rpc[mcp]
93
+ ```
94
+
95
+ This installs the official MCP SDK and required dependencies.
@@ -1,22 +1,22 @@
1
- import asyncio
2
-
3
- from pydantic_rpc import AsyncIOServer, Message
4
-
5
-
6
- class HelloRequest(Message):
7
- name: str
8
-
9
-
10
- class HelloReply(Message):
11
- message: str
12
-
13
-
14
- class Greeter:
15
- async def say_hello(self, request: HelloRequest) -> HelloReply:
16
- return HelloReply(message=f"Hello, {request.name}!")
17
-
18
-
19
- if __name__ == "__main__":
20
- s = AsyncIOServer()
21
- loop = asyncio.get_event_loop()
22
- loop.run_until_complete(s.run(Greeter()))
1
+ import asyncio
2
+
3
+ from pydantic_rpc import AsyncIOServer, Message
4
+
5
+
6
+ class HelloRequest(Message):
7
+ name: str
8
+
9
+
10
+ class HelloReply(Message):
11
+ message: str
12
+
13
+
14
+ class Greeter:
15
+ async def say_hello(self, request: HelloRequest) -> HelloReply:
16
+ return HelloReply(message=f"Hello, {request.name}!")
17
+
18
+
19
+ if __name__ == "__main__":
20
+ s = AsyncIOServer()
21
+ loop = asyncio.get_event_loop()
22
+ loop.run_until_complete(s.run(Greeter()))