EnrichMCP

repository·main·Indexed 20 days ago

https://github.com/featureform/enrichmcp

A Python framework that acts as an 'ORM for AI agents', enabling developers to transform data models—including SQLAlchemy, REST APIs, or custom logic—into a semantic Model Context Protocol (MCP) layer. It features built-in context caching with request, global, and user-scoped levels, automatic SQLAlchemy ORM conversion, and support for acting as an API gateway for existing services.

Tokens
36.6K
Snippets
120
Records
143
Agent score
69%

What's inside enrichmcp

  1. Overview of available EnrichMCP examples

    main

    The enrichmcp repository provides several examples to demonstrate different integration patterns:

    • hello_world: Minimal API.
    • hello_world_http: API served over streamable HTTP.
    • shop_api: In-memory e-commerce API with relationships and pagination.
    • shop_api_sqlite: SQLite-backed shop with cursor-based pagination.
    • sqlalchemy_shop: SQLAlchemy ORM integration with auto-generated CRUD.
    • shop_api_gateway: A gateway pattern forwarding requests to a FastAPI backend.
    • mutable_crud: Demonstrates mutable fields and CRUD decorators.
    • basic_memory: Note-taking API using FileMemoryStore.
    • caching: Demonstrates request caching with ContextCache.
    • openai_chat_agent: Interactive CLI agent using MCPAgent with conversation memory.
    • server_side_llm_travel_planner: LLM-backed travel suggestions.
  2. What is Agentic Enrichment?

    main

    Agentic Enrichment is a paradigm where AI agents can intelligently discover and navigate your data model without extensive documentation. Instead of manually teaching an AI about your API, the API uses EnrichMCP to teach itself to the AI through:

    1. Self-Describing: Every entity, field, and relationship includes rich descriptions.
    2. Discoverable: AI agents can explore the entire data model through introspection.
    3. Navigable: Relationships allow natural traversal of the data graph.
    4. Type-Safe: Full validation ensures data integrity.
  3. How EnrichMCP works with SQLAlchemy models

    main

    EnrichMCP can automatically transform existing SQLAlchemy models into an AI-navigable API.

    To use this feature:

    1. Add EnrichSQLAlchemyMixin to your DeclarativeBase.
    2. Use the info parameter in mapped_column or relationship to provide descriptions that help the AI understand the schema.
    3. Initialize the EnrichMCP app using sqlalchemy_lifespan to manage the database connection and lifecycle.
    4. Register your models with the app using include_sqlalchemy_models(app, Base).

    This enables tools like explore_data_model(), automatic filtering (e.g., list_users(status='active')), and relationship navigation (e.g., user.orders).

    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")
    
    class Base(DeclarativeBase, EnrichSQLAlchemyMixin):
        pass
    
    class User(Base):
        """User account."""
        __tablename__ = "users"
        id: Mapped[int] = mapped_column(primary_key=True, info={"description": "Unique user ID"})
        orders: Mapped[list["Order"]] = relationship(
            back_populates="user", info={"description": "All orders for this user"}
        )
    
    class Order(Base):
        """Customer order."""
        __tablename__ = "orders"
        user_id: Mapped[int] = mapped_column(info={"description": "Owner user ID"})
        user: Mapped[User] = relationship(back_populates="orders")
    
    app = EnrichMCP(
        "E-commerce Data",
        "API generated from SQLAlchemy models",
        lifespan=sqlalchemy_lifespan(Base, engine, cleanup_db_file=True),
    )
    include_sqlalchemy_models(app, Base)
    
    if __name__ == "__main__":
        app.run()
  4. How to wrap REST APIs with EnrichMCP

    main

    You can wrap existing REST APIs by defining entities that inherit from EnrichModel.

    Key components:

    • @app.entity(): Decorator to define a data model (entity) that the AI can interact with.
    • Relationship: Used within an EnrichModel to define navigable links between entities (e.g., a Customer has many Orders).
    • @app.retrieve(): Decorator used to define how to fetch a specific entity by its ID or other parameters.
    • @Entity.relationship.resolver: Decorator used to define the logic for fetching related data when an AI agent navigates a relationship.

    This approach allows AI agents to traverse your API as if it were a local graph of objects.

    from typing import Literal
    from enrichmcp import EnrichMCP, EnrichModel, Relationship
    from pydantic import Field
    import httpx
    
    app = EnrichMCP("API Gateway", "Wrapper around existing REST APIs")
    http = httpx.AsyncClient(base_url="https://api.example.com")
    
    @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")
    
    @app.retrieve()
    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())
    
    @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()
  5. Entity Relationships in the Shop API Example

    main

    The example implements a complex e-commerce data model using the following bidirectional relationships:

    • User.orders: A user can have multiple orders.
    • Item.orders: An item can be included in multiple orders.
    • Order.user: An order belongs to a single user.
    • Order.items: An order can contain multiple items.
  6. Export all data using the PaginatedResult protocol

    main

    If you need to write generic utility functions (like an exporter) that can handle both page-based and cursor-based pagination, use the PaginatedResult protocol.

    By using result.get_next_params(), your loop can automatically update its parameters for the next iteration regardless of whether the underlying implementation uses pages or cursors.

    from enrichmcp import PaginatedResult
    from typing import Callable, Awaitable, TypeVar
    
    T = TypeVar("T")
    
    async def export_paginated_data(
        fetcher: Callable[..., Awaitable[PaginatedResult[T]]], **initial_params
    ) -> list[T]:
        """Export all pages of data."""
        all_items = []
        params = initial_params.copy()
    
        while True:
            result = await fetcher(**params)
            all_items.extend(result.items)
    
            if not result.has_next:
                break
    
            # Works for both page and cursor pagination
            params.update(result.get_next_params())
    
        return all_items
  7. Control LLM tool access with allow_tools

    main

    You can control the visibility of MCP tools to the client-side LLM using the allow_tools parameter. Setting this enables context-aware answers where the LLM can suggest reading or calling other resources.

    Supported values:

    • "none": The LLM cannot see any tools.
    • "thisServer": The LLM can see tools provided by the current server.
    • "allServers": The LLM can see tools from all connected MCP servers.
  8. Define relationships between entities using Relationship

    main

    To create links between entities (e.g., an Author having many Books), use the Relationship type within your EnrichModel. This allows the API to navigate between related data points.

    from enrichmcp import EnrichModel, Relationship
    
    class Author(EnrichModel):
        # ... fields ...
        books: list["Book"] = Relationship(description="Books written by this author")
    
    class Book(EnrichModel):
        # ... fields ...
        author: Author = Relationship(description="Author of this book")
    @app.entity()
    class Author(EnrichModel):
        """Represents a book author."""
    
        id: int = Field(description="Author ID")
        name: str = Field(description="Author's full name")
        bio: str = Field(description="Short biography")
        birth_date: date = Field(description="Date of birth")
    
        # Relationship to books
        books: list["Book"] = Relationship(description="Books written by this author")
  9. Use ctx.sampling() to request LLM assistance in EnrichMCP

    main

    The ctx.sampling() helper allows an EnrichMCP server to request language model assistance from the client during a tool execution. This is useful for tasks like reasoning, summarizing, or selecting items based on natural language preferences.

    In this pattern, the server does not call an LLM directly; instead, it asks the MCP client (which holds the LLM connection) to perform the sampling task. This ensures the server remains agnostic of the specific LLM being used by the client.

    # Conceptual usage pattern
    # The server uses ctx.sampling() to ask the client for LLM help
    # Example: picking destinations based on user preferences
  10. How EnrichMCP works

    main

    EnrichMCP transforms your data models into an MCP (Model Context Protocol) API designed specifically for AI agents. It works through a structured lifecycle:

    1. Model Definition: You define Pydantic models decorated with @app.entity(). These models are self-describing, meaning they include rich descriptions that AI agents can use to understand the schema.
    2. Relationship Mapping: You connect entities using Relationship. This allows AI agents to perform natural data traversal (e.g., moving from a Customer to their Orders).
    3. Data Resolution: You implement @Relationship.resolver methods to provide the actual logic for fetching related data from your database or external service.
    4. Resource Exposure: You define entry points using @app.retrieve(), which allows agents to access specific records.
    5. Agent Interaction: Once running, AI agents can call explore_data_model() to discover the schema, use resources to access data, and follow relationships to traverse the data graph.
  11. Define data models using EnrichModel

    main

    Entities are the core data models in enrichmcp. They extend Pydantic's BaseModel to provide relationship support and introspection capabilities. To create an entity, inherit from EnrichModel and use the @app.entity decorator to register it with your application.

    All fields should include a description via Pydantic's Field to ensure proper introspection and documentation generation.

    from enrichmcp import EnrichModel
    from pydantic import Field
    
    @app.entity
    class MyEntity(EnrichModel):
        id: int = Field(description="Unique identifier")
  12. Enable schema introspection for AI agents

    main

    EnrichMCP automatically provides an explore_data_model() resource. This allows AI agents to programmatically discover your entire data model, including entities, fields, and relationships, which improves their ability to use your API correctly.

    # Automatically available to AI agents
    result = await explore_data_model()
    # Returns comprehensive schema information