restmcp 0.1.2__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.
- restmcp-0.1.2/.github/workflows/publish.yml +53 -0
- restmcp-0.1.2/.gitignore +51 -0
- restmcp-0.1.2/LICENSE +21 -0
- restmcp-0.1.2/PKG-INFO +406 -0
- restmcp-0.1.2/README.md +372 -0
- restmcp-0.1.2/conftest.py +4 -0
- restmcp-0.1.2/example/datasource/__init__.py +0 -0
- restmcp-0.1.2/example/datasource/posts_async.py +21 -0
- restmcp-0.1.2/example/datasource/posts_sync.py +19 -0
- restmcp-0.1.2/example/main.py +7 -0
- restmcp-0.1.2/example/models/__init__.py +0 -0
- restmcp-0.1.2/example/models/post.py +8 -0
- restmcp-0.1.2/example/pyproject.toml +14 -0
- restmcp-0.1.2/example/repositories/__init__.py +0 -0
- restmcp-0.1.2/example/repositories/posts.py +17 -0
- restmcp-0.1.2/example/repositories/posts_async.py +17 -0
- restmcp-0.1.2/example/services/__init__.py +0 -0
- restmcp-0.1.2/example/services/posts.py +25 -0
- restmcp-0.1.2/example/test_example.py +213 -0
- restmcp-0.1.2/example/tools/__init__.py +0 -0
- restmcp-0.1.2/example/urls/__init__.py +7 -0
- restmcp-0.1.2/example/urls/posts.py +76 -0
- restmcp-0.1.2/pyproject.toml +48 -0
- restmcp-0.1.2/restmcp/__init__.py +22 -0
- restmcp-0.1.2/restmcp/cli/__init__.py +11 -0
- restmcp-0.1.2/restmcp/cli/new.py +65 -0
- restmcp-0.1.2/restmcp/datasource.py +18 -0
- restmcp-0.1.2/restmcp/endpoint.py +140 -0
- restmcp-0.1.2/restmcp/entity.py +13 -0
- restmcp-0.1.2/restmcp/exceptions.py +17 -0
- restmcp-0.1.2/restmcp/logging.py +35 -0
- restmcp-0.1.2/restmcp/mcp.py +66 -0
- restmcp-0.1.2/restmcp/repository.py +35 -0
- restmcp-0.1.2/restmcp/rest.py +63 -0
- restmcp-0.1.2/restmcp/server.py +51 -0
- restmcp-0.1.2/restmcp/service.py +35 -0
- restmcp-0.1.2/restmcp/types.py +22 -0
- restmcp-0.1.2/tests/conftest.py +8 -0
- restmcp-0.1.2/tests/test_cli.py +54 -0
- restmcp-0.1.2/tests/test_datasource.py +28 -0
- restmcp-0.1.2/tests/test_endpoint.py +380 -0
- restmcp-0.1.2/tests/test_entity.py +36 -0
- restmcp-0.1.2/tests/test_exceptions.py +36 -0
- restmcp-0.1.2/tests/test_logger.py +67 -0
- restmcp-0.1.2/tests/test_mcp.py +168 -0
- restmcp-0.1.2/tests/test_repository.py +86 -0
- restmcp-0.1.2/tests/test_server.py +128 -0
- restmcp-0.1.2/tests/test_service.py +97 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Test & Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.13"
|
|
15
|
+
- run: pip install -e ".[dev]"
|
|
16
|
+
- run: pytest --cov=restmcp --cov-report=xml
|
|
17
|
+
- uses: codecov/codecov-action@v4
|
|
18
|
+
with:
|
|
19
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
20
|
+
files: coverage.xml
|
|
21
|
+
|
|
22
|
+
publish-testpypi:
|
|
23
|
+
needs: test
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: testpypi
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.13"
|
|
33
|
+
- run: pip install build
|
|
34
|
+
- run: python -m build
|
|
35
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
36
|
+
with:
|
|
37
|
+
repository-url: https://test.pypi.org/legacy/
|
|
38
|
+
skip-existing: true
|
|
39
|
+
|
|
40
|
+
publish-pypi:
|
|
41
|
+
needs: publish-testpypi
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
environment: pypi
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
- uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.13"
|
|
51
|
+
- run: pip install build
|
|
52
|
+
- run: python -m build
|
|
53
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
restmcp-0.1.2/.gitignore
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
wheels/
|
|
12
|
+
*.whl
|
|
13
|
+
MANIFEST
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
.env/
|
|
20
|
+
|
|
21
|
+
# Environment variables
|
|
22
|
+
.env
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
|
|
26
|
+
# pytest / coverage
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
.coverage.*
|
|
30
|
+
htmlcov/
|
|
31
|
+
coverage.xml
|
|
32
|
+
|
|
33
|
+
# Type checkers
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.dmypy.json
|
|
36
|
+
.pytype/
|
|
37
|
+
.pyre/
|
|
38
|
+
|
|
39
|
+
# IDEs
|
|
40
|
+
.idea/
|
|
41
|
+
.vscode/
|
|
42
|
+
*.swp
|
|
43
|
+
*.swo
|
|
44
|
+
*~
|
|
45
|
+
.DS_Store
|
|
46
|
+
|
|
47
|
+
# hatch / build
|
|
48
|
+
.hatch/
|
|
49
|
+
|
|
50
|
+
# internal dev docs
|
|
51
|
+
docs/superpowers/
|
restmcp-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JorgeHSantana
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
restmcp-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: restmcp
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Python framework for building MCP servers with a layered architecture and REST compatibility
|
|
5
|
+
Project-URL: Homepage, https://github.com/JorgeHSantana/restmcp
|
|
6
|
+
Project-URL: Repository, https://github.com/JorgeHSantana/restmcp
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/JorgeHSantana/restmcp/issues
|
|
8
|
+
Author-email: Jorge Henrique Moreira Santana <jorge.henrique.moreira.santana@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,async,fastapi,framework,llm,mcp,rest,server
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: click>=8.0
|
|
24
|
+
Requires-Dist: fastapi>=0.100
|
|
25
|
+
Requires-Dist: fastmcp>=2.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.20
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: httpx>=0.24; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
32
|
+
Requires-Dist: requests>=2.28; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# restmcp
|
|
36
|
+
|
|
37
|
+
[](https://codecov.io/gh/JorgeHSantana/restmcp)
|
|
38
|
+
|
|
39
|
+
> One framework. MCP tools and REST endpoints, auto-registered.
|
|
40
|
+
|
|
41
|
+
Python framework for building **MCP servers** with a layered architecture and REST compatibility.
|
|
42
|
+
Annotated classes become MCP tools and HTTP endpoints: auto-registered, dependency-injected, sync/async agnostic.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
graph LR
|
|
50
|
+
LLM["🤖 LLM / Client"] -->|"HTTP or MCP"| EP["Endpoint"]
|
|
51
|
+
EP --> SV["Service"]
|
|
52
|
+
SV --> RP["Repository"]
|
|
53
|
+
RP --> DS["DataSource"]
|
|
54
|
+
DS --> EX[("External\nAPI / DB")]
|
|
55
|
+
|
|
56
|
+
style EP fill:#4f46e5,color:#fff,stroke:none
|
|
57
|
+
style SV fill:#7c3aed,color:#fff,stroke:none
|
|
58
|
+
style RP fill:#9333ea,color:#fff,stroke:none
|
|
59
|
+
style DS fill:#a855f7,color:#fff,stroke:none
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each layer knows only the layer directly below it. Every class name is suffix-enforced at import time: a typo raises `TypeError` before the server starts.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install restmcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
restmcp new my-server
|
|
78
|
+
cd my-server
|
|
79
|
+
pip install -e .
|
|
80
|
+
python main.py
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Generated structure:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
my-server/
|
|
87
|
+
├── datasource/ # external connections (APIs, databases)
|
|
88
|
+
├── models/ # domain entities (Pydantic)
|
|
89
|
+
├── repositories/ # data access layer
|
|
90
|
+
├── services/ # business logic
|
|
91
|
+
├── tools/ # internal utilities
|
|
92
|
+
├── urls/ # endpoint definitions (auto-discovery)
|
|
93
|
+
├── main.py
|
|
94
|
+
└── pyproject.toml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## How it works
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
sequenceDiagram
|
|
103
|
+
participant C as Client / LLM
|
|
104
|
+
participant E as Endpoint
|
|
105
|
+
participant S as Service
|
|
106
|
+
participant R as Repository
|
|
107
|
+
participant D as DataSource
|
|
108
|
+
|
|
109
|
+
C->>E: POST /api/get-product {"product_id": "1"}
|
|
110
|
+
E->>S: service.execute(product_id="1")
|
|
111
|
+
S->>R: repo.get(product_id="1")
|
|
112
|
+
R->>D: data_source.fetch("1")
|
|
113
|
+
D-->>R: raw dict
|
|
114
|
+
R-->>S: ProductEntity
|
|
115
|
+
S-->>E: result dict
|
|
116
|
+
E-->>C: {"tool": "get_product", "result": {...}, "success": true}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Base classes
|
|
122
|
+
|
|
123
|
+
### `DataSource`
|
|
124
|
+
|
|
125
|
+
Abstracts the connection to an external data source (REST API, database, file).
|
|
126
|
+
**Rule:** class name must end with `DataSource`.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import httpx
|
|
130
|
+
from restmcp import DataSource
|
|
131
|
+
|
|
132
|
+
class ProductApiDataSource(DataSource):
|
|
133
|
+
base_url = "https://api.example.com"
|
|
134
|
+
|
|
135
|
+
async def fetch(self, product_id: str) -> dict:
|
|
136
|
+
async with httpx.AsyncClient() as client:
|
|
137
|
+
r = await client.get(f"{self.base_url}/products/{product_id}")
|
|
138
|
+
r.raise_for_status()
|
|
139
|
+
return r.json()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### `Entity`
|
|
145
|
+
|
|
146
|
+
Structured domain data backed by Pydantic. Automatic type validation.
|
|
147
|
+
**Rule:** class name must end with `Entity`.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from restmcp import Entity
|
|
151
|
+
|
|
152
|
+
class ProductEntity(Entity):
|
|
153
|
+
id: str
|
|
154
|
+
name: str
|
|
155
|
+
price: float
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### `Repository`
|
|
161
|
+
|
|
162
|
+
Fetches data via a `DataSource` and returns `Entity` objects. One source, one data type.
|
|
163
|
+
**Rules:** name ends with `Repository`; must declare `data_source` as class attribute; must implement `get()`.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from restmcp import Repository
|
|
167
|
+
from datasource.product_api import ProductApiDataSource
|
|
168
|
+
from models.product import ProductEntity
|
|
169
|
+
|
|
170
|
+
class ProductRepository(Repository):
|
|
171
|
+
data_source = ProductApiDataSource()
|
|
172
|
+
|
|
173
|
+
async def get(self, product_id: str) -> ProductEntity:
|
|
174
|
+
raw = await self.data_source.fetch(product_id)
|
|
175
|
+
return ProductEntity(**raw)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Dependency injection:**
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
repo = ProductRepository() # uses real DataSource
|
|
182
|
+
repo = ProductRepository(data_source=MockDataSource()) # injects mock for tests
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`Repository.__init__` uses `copy.copy()` of the class attribute: instances are always isolated.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `Service`
|
|
190
|
+
|
|
191
|
+
Orchestrates business logic. Where joins, transformations, and multi-source rules live.
|
|
192
|
+
**Rules:** name ends with `Service`; must declare at least one `Repository` as class attribute.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from restmcp import Service
|
|
196
|
+
from repositories.product import ProductRepository
|
|
197
|
+
|
|
198
|
+
class GetProductService(Service):
|
|
199
|
+
repo = ProductRepository()
|
|
200
|
+
|
|
201
|
+
async def execute(self, product_id: str) -> dict:
|
|
202
|
+
product = await self.repo.get(product_id=product_id)
|
|
203
|
+
return product.model_dump()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Dependency injection:**
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
svc = GetProductService() # production
|
|
210
|
+
svc = GetProductService(repo=MockRepository()) # test
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Repository class attributes are auto-discovered via MRO and isolated per instance.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### `Endpoint`
|
|
218
|
+
|
|
219
|
+
HTTP + MCP route. **Auto-registers on class definition**: no manual wiring needed.
|
|
220
|
+
**Rules:** name ends with `Endpoint`; must declare `mcp_definition`, `url`, `method`, and `callback`.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from restmcp import Endpoint
|
|
224
|
+
from services.product import GetProductService
|
|
225
|
+
|
|
226
|
+
class GetProductEndpoint(Endpoint):
|
|
227
|
+
mcp_definition = {
|
|
228
|
+
"name": "get_product",
|
|
229
|
+
"description": "Returns a product by ID",
|
|
230
|
+
"parameters": {
|
|
231
|
+
"properties": {
|
|
232
|
+
"product_id": {"type": "string", "description": "Product ID"},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
url = "/api/get-product"
|
|
237
|
+
method = "POST"
|
|
238
|
+
|
|
239
|
+
async def callback(self, product_id: str) -> dict:
|
|
240
|
+
return await GetProductService().execute(product_id)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Defining the class is enough. The route is registered on the `Server` singleton the moment Python processes the class body.
|
|
244
|
+
|
|
245
|
+
**Disabling an endpoint:**
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
class GetProductEndpoint(Endpoint):
|
|
249
|
+
disabled = True # skips auto-registration; can still be instantiated manually
|
|
250
|
+
...
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Abstract base classes** (missing any required attribute) are never auto-registered:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
class BaseAuthEndpoint(Endpoint):
|
|
257
|
+
method = "POST"
|
|
258
|
+
def callback(self, **kwargs): ...
|
|
259
|
+
# ↑ not registered: url and mcp_definition are missing
|
|
260
|
+
|
|
261
|
+
class GetUserEndpoint(BaseAuthEndpoint):
|
|
262
|
+
mcp_definition = { ... }
|
|
263
|
+
url = "/api/get-user"
|
|
264
|
+
# ↑ registered automatically: all required attributes present
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Sync and async callbacks** are both supported: restmcp detects and handles either:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
# sync: runs in a thread pool, does not block the event loop
|
|
271
|
+
def callback(self, product_id: str) -> dict:
|
|
272
|
+
return requests.get(f"https://api.example.com/products/{product_id}").json()
|
|
273
|
+
|
|
274
|
+
# async: awaited directly; use asyncio.gather for parallel I/O
|
|
275
|
+
async def callback(self, product_id: str) -> dict:
|
|
276
|
+
async with httpx.AsyncClient() as client:
|
|
277
|
+
r = await client.get(f"https://api.example.com/products/{product_id}")
|
|
278
|
+
return r.json()
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Response format:**
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{ "tool": "get_product", "result": { ... }, "success": true }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{ "tool": "get_product", "error": "not found", "error_type": "NotFoundError", "success": false }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### `Server`
|
|
294
|
+
|
|
295
|
+
Singleton with dual-mode: HTTP via FastAPI/uvicorn or MCP protocol via FastMCP.
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from restmcp import Server
|
|
299
|
+
import urls # triggers auto-discovery of all endpoint modules
|
|
300
|
+
|
|
301
|
+
server = Server.get_instance()
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
server.start(host="0.0.0.0", port=5000)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
# MCP mode
|
|
309
|
+
mcp = server.get_mcp()
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Built-in routes:**
|
|
313
|
+
|
|
314
|
+
| Route | Method | Auth required |
|
|
315
|
+
|-------|--------|---------------|
|
|
316
|
+
| `/health` | GET | No |
|
|
317
|
+
| `/mcp/tools` | GET | No |
|
|
318
|
+
| _your endpoints_ | POST | Yes (if `AUTH_API_KEY` is set) |
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Exceptions
|
|
323
|
+
|
|
324
|
+
Raised inside `callback`: caught by `Endpoint` and converted to HTTP responses automatically.
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from restmcp import ValidationError, NotFoundError
|
|
328
|
+
|
|
329
|
+
raise ValidationError("product_id is required") # → HTTP 400
|
|
330
|
+
raise NotFoundError("Product not found") # → HTTP 404
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```mermaid
|
|
334
|
+
graph TD
|
|
335
|
+
PythiaException --> ValidationError["ValidationError (400)"]
|
|
336
|
+
PythiaException --> NotFoundError["NotFoundError (404)"]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Testing with injection
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from restmcp import DataSource
|
|
345
|
+
from repositories.product import ProductRepository
|
|
346
|
+
from services.product import GetProductService
|
|
347
|
+
|
|
348
|
+
class FakeProductApiDataSource(DataSource):
|
|
349
|
+
async def fetch(self, product_id: str) -> dict:
|
|
350
|
+
return {"id": product_id, "name": "Test Widget", "price": 1.99}
|
|
351
|
+
|
|
352
|
+
def test_get_product():
|
|
353
|
+
svc = GetProductService(repo=ProductRepository(data_source=FakeProductApiDataSource()))
|
|
354
|
+
result = svc.execute(product_id="1")
|
|
355
|
+
assert result["name"] == "Test Widget"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Environment variables
|
|
361
|
+
|
|
362
|
+
| Variable | Default | Description |
|
|
363
|
+
|----------|---------|-------------|
|
|
364
|
+
| `AUTH_API_KEY` | _(disabled)_ | Bearer token. Multiple keys supported comma-separated. |
|
|
365
|
+
| `CORS_ORIGINS` | `*` | Allowed origins. Multiple values supported comma-separated. |
|
|
366
|
+
| `LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`. |
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Naming conventions
|
|
371
|
+
|
|
372
|
+
All base classes enforce a suffix. Violating it raises `TypeError` at import time: before the server starts.
|
|
373
|
+
|
|
374
|
+
| Base class | Required suffix | Example |
|
|
375
|
+
|------------|----------------|---------|
|
|
376
|
+
| `DataSource` | `*DataSource` | `ProductApiDataSource` |
|
|
377
|
+
| `Entity` | `*Entity` | `ProductEntity` |
|
|
378
|
+
| `Repository` | `*Repository` | `ProductRepository` |
|
|
379
|
+
| `Service` | `*Service` | `GetProductService` |
|
|
380
|
+
| `Endpoint` | `*Endpoint` | `GetProductEndpoint` |
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Dependencies
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
fastapi >= 0.100
|
|
388
|
+
uvicorn >= 0.20
|
|
389
|
+
fastmcp >= 2.0
|
|
390
|
+
pydantic >= 2.0
|
|
391
|
+
click >= 8.0
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Author
|
|
397
|
+
|
|
398
|
+
**Jorge Henrique Moreira Santana**
|
|
399
|
+
Electrical Engineer, Postgraduate in Artificial Intelligence
|
|
400
|
+
[LinkedIn](https://www.linkedin.com/in/jorge-santana-b246874a/) · jorge.henrique.moreira.santana@gmail.com
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## License
|
|
405
|
+
|
|
406
|
+
[MIT](LICENSE)
|