EnrichMCP The ORM for AI Agents - Turn your data model into a semantic MCP layer EnrichMCP is a Python framework that helps AI agents understand and navigate your data. Built on MCP (Model Context Protocol), it adds a semantic layer that turns your data model into typed, discoverable tools - like an ORM for AI. What is EnrichMCP? Think of it as SQLAlchemy for AI agents. EnrichMCP automatically: Generates typed tools from your data models from your data models Handles relationships between entities (users → orders → products) between entities (users → orders → products) Provides schema discovery so AI agents understand your data structure so AI agents understand your data structure Validates all inputs/outputs with Pydantic models with Pydantic models Works with any backend - databases, APIs, or custom logic Installation pip install enrichmcp # With SQLAlchemy support pip install enrichmcp[sqlalchemy] Show Me Code Option 1: I Have SQLAlchemy Models (30 seconds) Transform your existing SQLAlchemy models into an AI-navigable API: from enrichmcp import EnrichMCP from enrichmcp . sqlalchemy import include_sqlalchemy_models , sqlalchemy_lifespan , EnrichSQLAlchemyMixin from sqlalchemy . ext . asyncio import create_async_engine from sqlalchemy . orm import DeclarativeBase , Mapped , mapped_column , relationship engine = create_async_engine ( "postgresql+asyncpg://user:pass@localhost/db" ) # Add the mixin to your declarative base class Base ( DeclarativeBase , EnrichSQLAlchemyMixin ): pass class User ( Base ): __tablename__ = "users" id : Mapped [ int ] = mapped_column ( primary_key = True ) email : Mapped [ str ] = mapped_column ( unique = True ) status : Mapped [ str ] = mapped_column ( default = "active" ) orders : Mapped [ list [ "Order" ]] = relationship ( back_populates = "user" ) class Order ( Base ): __tablename__ = "orders" id : Mapped [ int ] = mapped_column ( primary_key = True ) user_id : Mapped [ int ] = mapped_column ( ForeignKey ( "users.id" )) total : Mapped [ float ] = mapped_column () user : Mapped [ User ] = relationship ( back_populates = "orders" ) # That's it! Create your MCP app app = EnrichMCP ( "E-commerce Data" , lifespan = sqlalchemy_lifespan ( Base , engine , cleanup_db_file = True ), ) include_sqlalchemy_models ( app , Base ) if __name__ == "__main__" : app . run () AI agents can now: explore_data_model() - understand your entire schema - understand your entire schema list_users(status='active') - query with filters - query with filters get_user(id=123) - fetch specific records - fetch specific records Navigate relationships: user.orders → order.user Option 2: I Have REST APIs (2 minutes) Wrap your existing APIs with semantic understanding: from enrichmcp import EnrichMCP , EnrichModel , Relationship from pydantic import Field app = EnrichMCP ( "API Gateway" ) @ app . entity class Customer ( EnrichModel ): """Customer in our CRM system.""" id : int = Field ( description = "Unique customer ID" ) email : str = Field ( description = "Primary contact email" ) tier : str = Field ( description = "Subscription tier: free, pro, enterprise" ) # Define navigable relationships orders : list [ "Order" ] = Relationship ( description = "Customer's purchase history" ) @ app . entity class Order ( EnrichModel ): """Customer order from our e-commerce platform.""" id : int = Field ( description = "Order ID" ) customer_id : int = Field ( description = "Associated customer" ) total : float = Field ( description = "Order total in USD" ) status : str = Field ( description = "Order status: pending, shipped, delivered" ) customer : Customer = Relationship ( description = "Customer who placed this order" ) # Define how to fetch data @ app . resource async def get_customer ( customer_id : int ) -> Customer : """Fetch customer from CRM API.""" response = await http . get ( f"/api/customers/ { customer_id } " ) return Customer ( ** response . json ()) # Define relationship resolvers @ Customer . orders . resolver async def get_customer_orders ( customer_id : int ) -> list [ Order ]: """Fetch orders for a customer.""" response = await http . get ( f"/api/customers/ { customer_id } /orders" ) return [ Order ( ** order ) for order in response . json ()] app . run () Option 3: I Want Full Control (5 minutes) Build a complete data layer with custom logic: from enrichmcp import EnrichMCP , EnrichModel , Relationship , EnrichContext from datetime import datetime from decimal import Decimal app = EnrichMCP ( "Analytics Platform" ) @ app . entity class User ( EnrichModel ): """User with computed analytics fields.""" id : int = Field ( description = "User ID" ) email : str = Field ( description = "Contact email" ) created_at : datetime = Field ( description = "Registration date" ) # Computed fields lifetime_value : Decimal = Field ( description = "Total revenue from user" ) churn_risk : float = Field ( description = "ML-predicted churn probability 0-1" ) # Relationships orders : list [ "Order" ] = Relationship ( description = "Purchase history" ) segments : list [ "Segment" ] = Relationship ( description = "Marketing segments" ) @ app . entity class Segment ( EnrichModel ): """Dynamic user segment for marketing.""" name : str = Field ( description = "Segment name" ) criteria : dict = Field ( description = "Segment criteria" ) users : list [ User ] = Relationship ( description = "Users in this segment" ) # Complex resource with business logic @ app . resource async def find_high_value_at_risk_users ( lifetime_value_min : Decimal = 1000 , churn_risk_min : float = 0.7 , limit : int = 100 ) -> list [ User ]: """Find valuable customers likely to churn.""" users = await db . query ( """ SELECT * FROM users WHERE lifetime_value >= ? AND churn_risk >= ? ORDER BY lifetime_value DESC LIMIT ? """ , lifetime_value_min , churn_risk_min , limit ) return [ User ( ** u ) for u in users ] # Async computed field resolver @ User . lifetime_value . resolver async def calculate_lifetime_value ( user_id : int ) -> Decimal : """Calculate total revenue from user's orders.""" total = await db . query_single ( "SELECT SUM(total) FROM orders WHERE user_id = ?" , user_id ) return Decimal ( str ( total or 0 )) # ML-powered field @ User . churn_risk . resolver async def predict_churn_risk ( user_id : int , context : EnrichContext ) -> float : """Run churn prediction model.""" features = await gather_user_features ( user_id ) model = context . get ( "ml_models" )[ "churn" ] return float ( model . predict_proba ( features )[ 0 ][ 1 ]) app . run () Key Features 🔍 Automatic Schema Discovery AI agents explore your entire data model with one call: schema = await explore_data_model () # Returns complete schema with entities, fields, types, and relationships 🔗 Relationship Navigation Define relationships once, AI agents traverse naturally: # AI can navigate: user → orders → products → categories user = await get_user ( 123 ) orders = await user . orders () # Automatic resolver products = await orders [ 0 ]. products () 🛡️ Type Safety & Validation Full Pydantic validation on every interaction: @ app . entity class Order ( EnrichModel ): total : float = Field ( ge = 0 , description = "Must be positive" ) email : EmailStr = Field ( description = "Customer email" ) status : Literal [ "pending" , "shipped" , "delivered" ] ✏️ Mutability & CRUD Fields are immutable by default. Mark them as mutable and use auto-generated patch models for updates: @ app . entity class Customer ( EnrichModel ): id : int = Field ( description = "ID" ) email : str = Field ( mutable = True , description = "Email" ) @ app . create async def create_customer ( email : str ) -> Customer : ... @ app . update async def update_customer ( cid : int , patch : Customer . PatchModel ) -> Customer : ... @ app . delete async def delete_customer ( cid : int ) -> bool : ... 📄 Pagination Built-in Handle large datasets elegantly: from enrichmcp import PageResult @ app . resource async def list_orders ( page : int = 1 , page_size : int = 50 ) -> PageResult [ Order ]: orders , total = await db . get_orders_page ( page , page_size ) return PageResult . create ( items = orders , page = page , page_size = page_size , total_items = total ) See the Pagination Guide for more examples. 🔐 Context & Authentication Pass auth, database connections, or any context: @ app . resource async def get_user_profile ( user_id : int , context : EnrichContext ) -> UserProfile : # Access context provided by MCP client auth_user = context . get ( "authenticated_user_id" ) if auth_user != user_id : raise PermissionError ( "Can only access your own profile" ) return await db . get_profile ( user_id ) Why EnrichMCP? EnrichMCP adds three critical layers on top of MCP: Semantic Layer - AI agents understand what your data means, not just its structure Data Layer - Type-safe models with validation and relationships Control Layer - Authentication, pagination, and business logic The result: AI agents can work with your data as naturally as a developer using an ORM. Examples Check out the examples directory: hello_world - The smallest possible EnrichMCP app shop_api - In-memory shop API with pagination and filters shop_api_sqlite - SQLite-backed version shop_api_gateway - EnrichMCP as a gateway in front of FastAPI sqlalchemy_shop - Auto-generated API from SQLAlchemy models Documentation Contributing We welcome contributions! See CONTRIBUTING.md for details. License Apache 2.0 - See LICENSE Built by Featureform • MCP Protocol