EnrichMCP: The Data Model Access Framework for AI Agents

In today’s digital era, artificial intelligence (AI) technology is evolving at an unprecedented pace. AI agents are being applied in various fields, and how to enable AI agents to better understand and process data has become a key issue. EnrichMCP, as a Python framework, provides an effective solution to this problem. Let’s take a detailed look at EnrichMCP.

1. Overview of EnrichMCP

1.1 What is EnrichMCP?

Simply put, EnrichMCP is like SQLAlchemy for AI agents. It is a Python framework built on the Model Context Protocol (MCP), primarily designed to help AI agents understand and process your data. By adding a semantic layer to your data models, EnrichMCP transforms your data models into typed, discoverable tools, acting as an Object-Relational Mapping (ORM) specifically tailored for AI.

1.2 Main Goals of EnrichMCP

EnrichMCP aims to:

  • Generate Typed Tools: Automatically generate typed tools from your data models, enabling AI agents to interact with data more accurately.
  • Manage Entity Relationships: Handle relationships between entities, such as the relationships between users, orders, and products, allowing AI agents to navigate naturally between these relationships.
  • Provide Schema Discovery: Offer schema discovery for AI agents, enabling them to understand the structure of the entire data model.
  • Validate Inputs and Outputs: Use Pydantic models to validate all inputs and outputs, ensuring data validity and consistency.
  • Support Any Backend: Work with any backend data source, including databases, APIs, or custom logic.

2. Project Structure of EnrichMCP

2.1 Project Layout

EnrichMCP features a clear project structure with well-defined components, facilitating development and maintenance for developers. Below is the main directory structure of the project:

/ (repo root)
├── README.md          – Project introduction and quickstart guide
├── Makefile           – Contains common development commands
├── pyproject.toml     – Project metadata and tool configuration
├── docs/              – User documentation (MkDocs-generated website)
├── examples/          – Runable code examples
├── src/enrichmcp/     – Implementation code of the library
└── tests/             – Unit test code

2.2 Important Files and Their Functions

2.2.1 pyproject.toml

This file defines the project’s metadata, including the required Python version, dependencies, optional development tools, and tool configurations such as Ruff, Pyright, and code coverage settings.

2.2.2 Makefile

It includes common development tasks, such as setting up the development environment, code checking, running tests, generating documentation, etc. For example, running the make setup command creates a virtual environment and installs the project’s dependencies.

2.2.3 docs/

This directory contains user documentation written in Markdown format. The documentation provides detailed descriptions of EnrichMCP’s core concepts, usage examples, pagination strategies, and SQLAlchemy integration, and can be converted into a website for browsing via MkDocs.

2.2.4 src/enrichmcp/

This is the directory where the core implementation code of the EnrichMCP framework resides. Here are some main modules and their functions:

  • app.py: Defines the EnrichMCP class, the main application class of the framework. During initialization, it stores metadata and creates a FastMCP instance, which serves as the core of the entire application, handling various requests and managing resources.
  • entity.py: Defines the EnrichModel class, which is a BaseModel based on Pydantic with additional helper functions. For example, it can detect relationship fields and remove them from serialized output to avoid recursion issues; it can also return a Markdown-formatted model description.
  • relationship.py: Implements the Relationship descriptor for declaring relationships between entities. It supports resolver registration and automatically creates MCP resources with descriptive names and docstrings. It also validates whether the return type of the resolver matches the annotated relationship type.
  • pagination.py: Defines pagination result types, such as PageResult for page-based pagination and CursorResult for cursor-based pagination. It also provides auxiliary parameter classes for implementing pagination in resources and resolvers.
  • context.py: A thin wrapper around the FastMCP context object.
  • lifespan.py: Provides a helper function for combining asynchronous lifespan functions.
  • sqlalchemy/: Contains tools for integrating with SQLAlchemy, such as automatically converting SQLAlchemy models into EnrichMCP entities and registering default resources and relationship resolvers.

2.2.5 examples/

This directory contains runnable code examples demonstrating different usage scenarios of EnrichMCP, such as a simple Hello World API, memory-based and SQLite-based shop APIs, an OpenAI chat agent, and SQLAlchemy integration.

2.2.6 tests/

This directory contains unit test code to verify the behavior of EnrichMCP applications, including entity registration, resource decoration, pagination tools, and model description generation.

3. Core Functions of EnrichMCP

3.1 EnrichMCP Application

3.1.1 Initialization and Metadata Storage

The EnrichMCP class defined in src/enrichmcp/app.py stores metadata and creates a FastMCP instance during initialization. This instance acts as the core of the entire application, handling various requests and managing resources.

3.1.2 Data Model Exploration Resource

EnrichMCP provides a built-in resource explore_data_model. By calling this resource, AI agents can obtain a comprehensive description of all registered entities, relationships, and usage hints, enabling AI agents to quickly understand the structure and usage of the entire data model.

3.1.3 Entity Registration

EnrichMCP uses the entity decorator to register entities. During registration, it validates whether the model has a description and whether all fields (except relationship fields) include Field(..., description="..."). Registered relationships are attached to the model class for subsequent resolution.

3.1.4 Model Description Generation

The describe_model method can generate documentation descriptions for each entity, its fields, and relationships, which is very useful for both AI agents and developers to understand the detailed information of the data model.

3.1.5 Resource Registration

The resource decorator is used to wrap functions as MCP resources. During registration, it requires the function to have a description and registers it with FastMCP, allowing AI agents to obtain data or perform operations by calling these resources.

3.1.6 Pre-run Validation

Before running the server, the run() method validates whether all relationships have at least one resolver. If not, it throws a ValueError exception to ensure the integrity and availability of the data model.

3.2 Entities and Relationships

3.2.1 Entities (EnrichModel)

The EnrichModel defined in src/enrichmcp/entity.py is a BaseModel based on Pydantic, adding some additional functions. For example, the relationship_fields() method can detect relationship fields and remove them from serialized output to avoid recursion issues. The describe() method returns a Markdown-formatted description including field and relationship information.

3.2.2 Relationships (Relationship)

The Relationship descriptor implemented in src/enrichmcp/relationship.py is used to declare relationships between entities. It supports resolver registration, and resolvers can be registered for relationship fields through the @Entity.field.resolver decorator. The resolver automatically creates MCP resources with descriptive names and docstrings and validates whether the return type of the resolver matches the annotated relationship type.

3.3 Pagination Tools

src/enrichmcp/pagination.py defines some pagination result types and auxiliary parameter classes for handling pagination of large datasets:

  • PageResult: Used for page-based pagination, including methods to calculate has_previous and total_pages, facilitating developers to implement pagination logic.
  • CursorResult: Used for cursor-based pagination, which can detect whether there is a next page and provide parameters for the next page.
  • PaginationParams and CursorParams: Provide auxiliary parameters for implementing pagination in resources and resolvers.

3.4 SQLAlchemy Integration

EnrichMCP provides integration with SQLAlchemy, making it easy for developers to convert existing SQLAlchemy models into EnrichMCP entities. The sqlalchemy package contains auxiliary tools such as EnrichSQLAlchemyMixin, include_sqlalchemy_models, and sqlalchemy_lifespan for database setup and model conversion.

4. Installation and Usage of EnrichMCP

4.1 Installation

You can install EnrichMCP using pip. If you only need basic functions, use the following command:

pip install enrichmcp

If you need to use SQLAlchemy integration functions, use the following command:

pip install enrichmcp[sqlalchemy]

4.2 Usage Examples

4.2.1 Existing SQLAlchemy Models

If you already have SQLAlchemy models, you can convert them into an AI-navigable API through the following steps:

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")

# Create the MCP application
app = EnrichMCP(
    "E-commerce Data",
    lifespan=sqlalchemy_lifespan(Base, engine, cleanup_db_file=True),
)
include_sqlalchemy_models(app, Base)

if __name__ == "__main__":
    app.run()

In this example, we first create an asynchronous SQLAlchemy engine, then define the User and Order models, and add EnrichSQLAlchemyMixin. Next, we create an EnrichMCP application and use the include_sqlalchemy_models method to integrate SQLAlchemy models into the application. Finally, we run the application.

4.2.2 Existing REST APIs

If you have existing REST APIs, you can use EnrichMCP to add semantic understanding to them. Here is an example:

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()

In this example, we first create an EnrichMCP application, then define the Customer and Order entities and the relationships between them. Next, we define a resource get_customer for fetching customer information from the CRM API and a relationship resolver get_customer_orders for fetching a customer’s orders. Finally, we run the application.

4.2.3 Full Custom Control

If you want to fully customize the data layer, you can use the following example:

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]

# Asynchronous 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))

# Machine learning-driven 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()

In this example, we create an EnrichMCP application and define the User and Segment entities and the relationships between them. We also define a complex resource find_high_value_at_risk_users for finding high-value customers likely to churn. In addition, we define resolvers for the computed fields lifetime_value and churn_risk of the User entity. Finally, we run the application.

5. Key Features of EnrichMCP

5.1 Automatic Schema Discovery

AI agents can explore the entire data model by calling the explore_data_model() method in one go. This method returns a complete schema containing all entities, fields, types, and relationships, allowing AI agents to quickly understand the structure of the data.

5.2 Relationship Navigation

EnrichMCP allows you to define relationships between entities, and AI agents can navigate naturally between these relationships. For example, AI can access related data through a path like user → orders → products → categories.

5.3 Type Safety and Validation

EnrichMCP uses Pydantic for comprehensive input and output validation to ensure data validity and consistency. For example, when defining entity fields, you can specify data types and validation rules, such as total: float = Field(ge=0, description="Must be positive"), which ensures that the input total value is a non-negative floating-point number.

5.4 Mutability and CRUD Operations

Entity fields are immutable by default, but you can mark them as mutable and use automatically generated patch models for updates. At the same time, EnrichMCP also supports create, update, and delete operations, and you can implement these functions by defining corresponding resource functions.

5.5 Built-in Pagination

EnrichMCP provides built-in pagination functionality for handling large datasets. You can use types such as PageResult and CursorResult to implement pagination based on page numbers or cursors. For example:

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
    )

For more pagination examples, refer to the Pagination Guide.

5.6 Context and Authentication

EnrichMCP allows you to pass authentication information, database connections, or any context. For example, in a resource function, you can access context information through the context parameter to implement authentication and permission control.

@app.resource
async def get_user_profile(user_id: int, context: EnrichContext) -> UserProfile:
    # Access context provided by the 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)

6. Advantages of EnrichMCP

6.1 Semantic Layer

EnrichMCP adds a semantic layer to data models, enabling AI agents to understand not only the structure of the data but also the meaning of the data. This helps AI agents process and analyze data more accurately.

6.2 Data Layer

By using type-safe models and validation mechanisms, EnrichMCP ensures data consistency and validity. At the same time, it also supports the management of relationships between entities, making the organization and access of data more natural and convenient.

6.3 Control Layer

EnrichMCP provides control functions such as authentication, pagination, and business logic, allowing developers to better manage and control the interaction between AI agents and data. This enables AI agents to interact with data as naturally as developers using an ORM.

7. Examples and Documentation of EnrichMCP

7.1 Examples

The examples directory of EnrichMCP contains several runnable examples demonstrating different usage scenarios:

  • hello_world: The smallest EnrichMCP application example.
  • shop_api: A memory-based shop API example with pagination and filtering functions.
  • shop_api_sqlite: A SQLite-based shop API example.
  • shop_api_gateway: An example of using EnrichMCP as a FastAPI front-end gateway.
  • sqlalchemy_shop: An example of automatically generating an API from SQLAlchemy models.

7.2 Documentation

EnrichMCP provides detailed documentation to help developers get started and use it quickly:

8. Frequently Asked Questions (FAQ)

8.1 Which backend data sources can EnrichMCP work with?

EnrichMCP can work with any backend data source, including databases, APIs, or custom logic. It provides integration with SQLAlchemy, making it easy for you to convert existing SQLAlchemy models into EnrichMCP entities.

8.2 How to install EnrichMCP?

You can install EnrichMCP using pip. If you only need basic functions, use pip install enrichmcp; if you need to use SQLAlchemy integration functions, use pip install enrichmcp[sqlalchemy].

8.3 How to define entities and relationships?

You can use the @app.entity decorator to define entities and the Relationship descriptor to define relationships between entities. For example:

@app.entity
class Customer(EnrichModel):
    id: int = Field(description="Unique customer ID")
    orders: list["Order"] = Relationship(description="Customer's purchase history")

@app.entity
class Order(EnrichModel):
    id: int = Field(description="Order ID")
    customer: Customer = Relationship(description="Customer who placed this order")

8.4 How to implement pagination?

EnrichMCP provides types such as PageResult and CursorResult for implementing pagination based on page numbers or cursors. You can use these types in resource functions to return paginated results. For example:

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
    )

8.5 How to perform input and output validation?

EnrichMCP uses Pydantic for input and output validation. You can specify data types and validation rules when defining entity fields, such as total: float = Field(ge=0, description="Must be positive"), which ensures that the input total value is a non-negative floating-point number.

9. Conclusion

EnrichMCP is a powerful Python framework that provides a structured way for AI agents to access and process data. By adding a semantic layer, a data layer, and a control layer, EnrichMCP enables AI agents to interact with data as naturally as developers using an ORM. It supports multiple backend data sources and provides rich functions such as automatic schema discovery, relationship navigation, type safety and validation, mutability and CRUD operations, built-in pagination, and context and authentication. At the same time, EnrichMCP also provides detailed documentation and rich examples, making it easy for developers to get started and use. If you are looking for a way to enable AI agents to better understand and process your data, EnrichMCP is definitely a choice worth considering.