@@ -39,17 +39,23 @@ Transform your existing SQLAlchemy models into an AI-navigable API:
3939
4040``` python
4141from enrichmcp import EnrichMCP
42- from enrichmcp.sqlalchemy import include_sqlalchemy_models, sqlalchemy_lifespan, EnrichSQLAlchemyMixin
42+ from enrichmcp.sqlalchemy import (
43+ include_sqlalchemy_models,
44+ sqlalchemy_lifespan,
45+ EnrichSQLAlchemyMixin,
46+ )
4347from sqlalchemy import ForeignKey
4448from sqlalchemy.ext.asyncio import create_async_engine
4549from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
4650
4751engine = create_async_engine(" postgresql+asyncpg://user:pass@localhost/db" )
4852
53+
4954# Add the mixin to your declarative base
5055class Base (DeclarativeBase , EnrichSQLAlchemyMixin ):
5156 pass
5257
58+
5359class User (Base ):
5460 """ User account."""
5561
@@ -58,17 +64,25 @@ class User(Base):
5864 id : Mapped[int ] = mapped_column(primary_key = True , info = {" description" : " Unique user ID" })
5965 email: Mapped[str ] = mapped_column(unique = True , info = {" description" : " Email address" })
6066 status: Mapped[str ] = mapped_column(default = " active" , info = {" description" : " Account status" })
61- orders: Mapped[list[" Order" ]] = relationship(back_populates = " user" , info = {" description" : " All orders for this user" })
67+ orders: Mapped[list[" Order" ]] = relationship(
68+ back_populates = " user" , info = {" description" : " All orders for this user" }
69+ )
70+
6271
6372class Order (Base ):
6473 """ Customer order."""
6574
6675 __tablename__ = " orders"
6776
6877 id : Mapped[int ] = mapped_column(primary_key = True , info = {" description" : " Order ID" })
69- user_id: Mapped[int ] = mapped_column(ForeignKey(" users.id" ), info = {" description" : " Owner user ID" })
78+ user_id: Mapped[int ] = mapped_column(
79+ ForeignKey(" users.id" ), info = {" description" : " Owner user ID" }
80+ )
7081 total: Mapped[float ] = mapped_column(info = {" description" : " Order total" })
71- user: Mapped[User] = relationship(back_populates = " orders" , info = {" description" : " User who placed the order" })
82+ user: Mapped[User] = relationship(
83+ back_populates = " orders" , info = {" description" : " User who placed the order" }
84+ )
85+
7286
7387# That's it! Create your MCP app
7488app = EnrichMCP(
@@ -100,52 +114,54 @@ import httpx
100114app = EnrichMCP(" API Gateway" , " Wrapper around existing REST APIs" )
101115http = httpx.AsyncClient(base_url = " https://api.example.com" )
102116
103- @app.entity
117+
118+ @app.entity ()
104119class Customer (EnrichModel ):
105120 """ Customer in our CRM system."""
106121
107122 id : int = Field(description = " Unique customer ID" )
108123 email: str = Field(description = " Primary contact email" )
109- tier: Literal[" free" , " pro" , " enterprise" ] = Field(
110- description = " Subscription tier"
111- )
124+ tier: Literal[" free" , " pro" , " enterprise" ] = Field(description = " Subscription tier" )
112125
113126 # Define navigable relationships
114127 orders: list[" Order" ] = Relationship(description = " Customer's purchase history" )
115128
116- @app.entity
129+
130+ @app.entity ()
117131class Order (EnrichModel ):
118132 """ Customer order from our e-commerce platform."""
119133
120134 id : int = Field(description = " Order ID" )
121135 customer_id: int = Field(description = " Associated customer" )
122136 total: float = Field(description = " Order total in USD" )
123- status: Literal[" pending" , " shipped" , " delivered" ] = Field(
124- description = " Order status"
125- )
137+ status: Literal[" pending" , " shipped" , " delivered" ] = Field(description = " Order status" )
126138
127139 customer: Customer = Relationship(description = " Customer who placed this order" )
128140
141+
129142# Define how to fetch data
130- @app.retrieve
143+ @app.retrieve ()
131144async def get_customer (customer_id : int ) -> Customer:
132145 """ Fetch customer from CRM API."""
133146 response = await http.get(f " /api/customers/ { customer_id} " )
134147 return Customer(** response.json())
135148
149+
136150# Define relationship resolvers
137151@Customer.orders.resolver
138152async def get_customer_orders (customer_id : int ) -> list[Order]:
139153 """ Fetch orders for a customer."""
140154 response = await http.get(f " /api/customers/ { customer_id} /orders " )
141155 return [Order(** order) for order in response.json()]
142156
157+
143158@Order.customer.resolver
144159async def get_order_customer (order_id : int ) -> Customer:
145160 """ Fetch the customer for an order."""
146161 response = await http.get(f " /api/orders/ { order_id} /customer " )
147162 return Customer(** response.json())
148163
164+
149165app.run()
150166```
151167
@@ -163,7 +179,8 @@ app = EnrichMCP("Analytics Platform", "Custom analytics API")
163179
164180db = ... # your database connection
165181
166- @app.entity
182+
183+ @app.entity ()
167184class User (EnrichModel ):
168185 """ User with computed analytics fields."""
169186
@@ -179,7 +196,8 @@ class User(EnrichModel):
179196 orders: list[" Order" ] = Relationship(description = " Purchase history" )
180197 segments: list[" Segment" ] = Relationship(description = " Marketing segments" )
181198
182- @app.entity
199+
200+ @app.entity ()
183201class Segment (EnrichModel ):
184202 """ Dynamic user segment for marketing."""
185203
@@ -188,14 +206,15 @@ class Segment(EnrichModel):
188206 users: list[User] = Relationship(description = " Users in this segment" )
189207
190208
191- @app.entity
209+ @app.entity ()
192210class Order (EnrichModel ):
193211 """ Simplified order record."""
194212
195213 id : int = Field(description = " Order ID" )
196214 user_id: int = Field(description = " Owner user ID" )
197215 total: Decimal = Field(description = " Order total" )
198216
217+
199218@User.orders.resolver
200219async def list_user_orders (user_id : int ) -> list[Order]:
201220 """ Fetch orders for a user."""
@@ -205,6 +224,7 @@ async def list_user_orders(user_id: int) -> list[Order]:
205224 )
206225 return [Order(** row) for row in rows]
207226
227+
208228@User.segments.resolver
209229async def list_user_segments (user_id : int ) -> list[Segment]:
210230 """ Fetch segments that include the user."""
@@ -214,6 +234,7 @@ async def list_user_segments(user_id: int) -> list[Segment]:
214234 )
215235 return [Segment(** row) for row in rows]
216236
237+
217238@Segment.users.resolver
218239async def list_segment_users (name : str ) -> list[User]:
219240 """ List users in a segment."""
@@ -223,12 +244,11 @@ async def list_segment_users(name: str) -> list[User]:
223244 )
224245 return [User(** row) for row in rows]
225246
247+
226248# Complex resource with business logic
227- @app.retrieve
249+ @app.retrieve ()
228250async def find_high_value_at_risk_users (
229- lifetime_value_min : Decimal = 1000 ,
230- churn_risk_min : float = 0.7 ,
231- limit : int = 100
251+ lifetime_value_min : Decimal = 1000 , churn_risk_min : float = 0.7 , limit : int = 100
232252) -> list[User]:
233253 """ Find valuable customers likely to churn."""
234254 users = await db.query(
@@ -238,20 +258,21 @@ async def find_high_value_at_risk_users(
238258 ORDER BY lifetime_value DESC
239259 LIMIT ?
240260 """ ,
241- lifetime_value_min, churn_risk_min, limit
261+ lifetime_value_min,
262+ churn_risk_min,
263+ limit,
242264 )
243265 return [User(** u) for u in users]
244266
267+
245268# Async computed field resolver
246269@User.lifetime_value.resolver
247270async def calculate_lifetime_value (user_id : int ) -> Decimal:
248271 """ Calculate total revenue from user's orders."""
249- total = await db.query_single(
250- " SELECT SUM(total) FROM orders WHERE user_id = ?" ,
251- user_id
252- )
272+ total = await db.query_single(" SELECT SUM(total) FROM orders WHERE user_id = ?" , user_id)
253273 return Decimal(str (total or 0 ))
254274
275+
255276# ML-powered field
256277@User.churn_risk.resolver
257278async def predict_churn_risk (user_id : int ) -> float :
@@ -261,6 +282,7 @@ async def predict_churn_risk(user_id: int) -> float:
261282 model = ctx.get(" ml_models" )[" churn" ]
262283 return float (model.predict_proba(features)[0 ][1 ])
263284
285+
264286app.run()
265287```
266288
@@ -291,7 +313,7 @@ products = await orders[0].products()
291313Full Pydantic validation on every interaction:
292314
293315``` python
294- @app.entity
316+ @app.entity ()
295317class Order (EnrichModel ):
296318 total: float = Field(ge = 0 , description = " Must be positive" )
297319 email: EmailStr = Field(description = " Customer email" )
@@ -305,22 +327,22 @@ Fields are immutable by default. Mark them as mutable and use
305327auto-generated patch models for updates:
306328
307329``` python
308- @app.entity
330+ @app.entity ()
309331class Customer (EnrichModel ):
310332 id : int = Field(description = " ID" )
311333 email: str = Field(json_schema_extra = {" mutable" : True }, description = " Email" )
312334
313- @app.create
314- async def create_customer (email : str ) -> Customer:
315- ...
316335
317- @app.update
318- async def update_customer ( cid : int , patch : Customer.PatchModel ) -> Customer:
319- ...
336+ @app.create ()
337+ async def create_customer ( email : str ) -> Customer: ...
338+
320339
321- @app.delete
322- async def delete_customer (cid : int ) -> bool :
323- ...
340+ @app.update ()
341+ async def update_customer (cid : int , patch : Customer.PatchModel) -> Customer: ...
342+
343+
344+ @app.delete ()
345+ async def delete_customer (cid : int ) -> bool : ...
324346```
325347
326348### 📄 Pagination Built-in
@@ -330,18 +352,11 @@ Handle large datasets elegantly:
330352``` python
331353from enrichmcp import PageResult
332354
333- @app.retrieve
334- async def list_orders (
335- page : int = 1 ,
336- page_size : int = 50
337- ) -> PageResult[Order]:
355+
356+ @app.retrieve ()
357+ async def list_orders (page : int = 1 , page_size : int = 50 ) -> PageResult[Order]:
338358 orders, total = await db.get_orders_page(page, page_size)
339- return PageResult.create(
340- items = orders,
341- page = page,
342- page_size = page_size,
343- total_items = total
344- )
359+ return PageResult.create(items = orders, page = page, page_size = page_size, total_items = total)
345360```
346361
347362See the [ Pagination Guide] ( https://featureform.github.io/enrichmcp/pagination ) for more examples.
@@ -354,13 +369,15 @@ Pass auth, database connections, or any context:
354369from pydantic import Field
355370from enrichmcp import EnrichModel
356371
372+
357373class UserProfile (EnrichModel ):
358374 """ User profile information."""
359375
360376 user_id: int = Field(description = " User ID" )
361377 bio: str | None = Field(default = None , description = " Short bio" )
362378
363- @app.retrieve
379+
380+ @app.retrieve ()
364381async def get_user_profile (user_id : int ) -> UserProfile:
365382 ctx = app.get_context()
366383 # Access context provided by MCP client
@@ -375,10 +392,10 @@ async def get_user_profile(user_id: int) -> UserProfile:
375392Reduce API overhead by storing results in a per-request, per-user, or global cache:
376393
377394``` python
378-
379- @app.retrieve
395+ @app.retrieve ()
380396async def get_customer (cid : int ) -> Customer:
381397 ctx = app.get_context()
398+
382399 async def fetch () -> Customer:
383400 return await db.get_customer(cid)
384401
@@ -392,7 +409,8 @@ Provide examples and metadata for tool parameters using `EnrichParameter`:
392409``` python
393410from enrichmcp import EnrichParameter
394411
395- @app.retrieve
412+
413+ @app.retrieve ()
396414async def greet_user (name : str = EnrichParameter(description = " user name" , examples = [" bob" ])) -> str :
397415 return f " Hello { name} "
398416```
0 commit comments