Skip to main content
Glama
renaisanci

mcp-clean-architecture

by renaisanci
README.md
# FastMCP Clean Architecture — MCP App UI Template

A production-oriented template for building **MCP Servers and MCP Apps with Python and FastMCP**, following **Clean Architecture**, Dependency Inversion, separation of concerns, and modern Python practices.

The project is also intended as a learning reference for developers coming from **C# / .NET**.

> The goal is not only to build an MCP server that works, but to build one that remains maintainable, testable, extensible, and independent from external frameworks and services.

---

## Goals

This template demonstrates how to build an MCP application with:

- Python
- FastMCP
- Streamable HTTP transport
- Stateless HTTP
- MCP Tools
- MCP Resources
- MCP Prompts
- MCP Apps / App UI
- Clean Architecture
- Dependency Inversion
- Repository Pattern
- Use Cases
- Pydantic models
- External REST API integrations
- Environment-based configuration
- Async HTTP communication
- Dependency Injection / Composition
- Centralized Error Handling
- Structured application errors
- Logging
- Unit tests
- Integration tests

The sample domain is an **e-commerce application**.

Products are retrieved from a public external API and exposed through MCP.

The application will evolve to support actions such as:

- Search products
- View product details
- Add products to a cart
- View the cart
- Remove products from the cart

An **MCP App UI** will provide an interactive experience inside compatible MCP hosts.

---

# Architecture

The project follows Clean Architecture principles.

```text
                         MCP HOST
                   Claude / Copilot / etc.
                              |
                              | MCP over HTTP
                              v
+---------------------------------------------------------+
|                    PRESENTATION                         |
|                                                         |
|  FastMCP Server                                         |
|  MCP Tools                                              |
|  MCP Resources                                          |
|  MCP Prompts                                            |
|  MCP App UI                                             |
|  Error Boundary                                         |
+---------------------------+-----------------------------+
                            |
                            v
+---------------------------------------------------------+
|                    APPLICATION                          |
|                                                         |
|  Use Cases                                              |
|                                                         |
|  GetProductUseCase                                      |
|  SearchProductsUseCase                                  |
|  AddProductToCartUseCase                                |
|  GetCartUseCase                                         |
+---------------------------+-----------------------------+
                            |
                            v
+---------------------------------------------------------+
|                       DOMAIN                            |
|                                                         |
|  Entities / Models                                      |
|                                                         |
|  Product                                                |
|  Cart                                                   |
|                                                         |
|  Repository Contracts                                   |
|                                                         |
|  ProductRepository                                      |
|  CartRepository                                         |
|                                                         |
|  Domain Errors                                          |
+---------------------------+-----------------------------+
                            ^
                            |
+---------------------------+-----------------------------+
|                   INFRASTRUCTURE                        |
|                                                         |
|  External API implementations                           |
|  HTTP clients                                           |
|  Configuration                                          |
|  Persistence adapters                                   |
|                                                         |
|  DummyJsonProductRepository                             |
|  DummyJsonCartRepository                                |
+---------------------------+-----------------------------+
                            |
                            v
                     External REST API
```

---

# Dependency Rule

The most important rule is:

```text
Presentation  ---> Application ---> Domain
                         ^
                         |
Infrastructure ----------+
```

Dependencies point toward the core application.

The **Domain must never depend on**:

```text
FastMCP
HTTP libraries
Uvicorn
DummyJSON
Claude
Copilot
databases
environment variables
MCP App UI
```

For example:

```text
MCP Tool
   |
   v
GetProductUseCase
   |
   v
ProductRepository
   ^
   |
DummyJsonProductRepository
   |
   v
DummyJSON REST API
```

`GetProductUseCase` knows about the `ProductRepository` abstraction.

It does **not** know that products are retrieved using HTTP or DummyJSON.

This allows:

```text
DummyJSON
```

to later be replaced with:

```text
SQL Server
PostgreSQL
MongoDB
another REST API
mock repository
```

without changing the application use case.

---

# Project Structure

The project will evolve toward the following structure:

```text
mcp-clean-architecture/
|
|-- src/
|   |
|   |-- domain/
|   |   |
|   |   |-- entities/
|   |   |   |-- __init__.py
|   |   |   |-- product.py
|   |   |   `-- cart.py
|   |   |
|   |   |-- repositories/
|   |   |   |-- __init__.py
|   |   |   |-- product_repository.py
|   |   |   `-- cart_repository.py
|   |   |
|   |   `-- errors/
|   |       |-- __init__.py
|   |       `-- domain_errors.py
|   |
|   |-- application/
|   |   |
|   |   |-- use_cases/
|   |   |   |-- __init__.py
|   |   |   |-- get_product.py
|   |   |   |-- search_products.py
|   |   |   |-- add_product_to_cart.py
|   |   |   `-- get_cart.py
|   |   |
|   |   `-- errors/
|   |       |-- __init__.py
|   |       `-- application_errors.py
|   |
|   |-- infrastructure/
|   |   |
|   |   |-- config/
|   |   |   |-- __init__.py
|   |   |   `-- environment.py
|   |   |
|   |   |-- http/
|   |   |
|   |   |-- repositories/
|   |   |   |-- __init__.py
|   |   |   |-- dummy_json_product_repository.py
|   |   |   `-- dummy_json_cart_repository.py
|   |   |
|   |   `-- errors/
|   |       |-- __init__.py
|   |       `-- infrastructure_errors.py
|   |
|   `-- presentation/
|       |
|       `-- mcp/
|           |-- __init__.py
|           |-- server.py
|           |
|           |-- tools/
|           |
|           |-- resources/
|           |
|           |-- prompts/
|           |
|           `-- apps/
|
|-- tests/
|   |
|   |-- unit/
|   `-- integration/
|
|-- .env.example
|-- .gitignore
|-- .python-version
|-- pyproject.toml
|-- uv.lock
`-- README.md
```

Folders should be introduced when they have a real responsibility.

The template should not create abstractions only for the sake of having more layers.

---

# Layer Responsibilities

## Domain

Contains the core business concepts and contracts.

Examples:

```text
Product
Cart

ProductRepository
CartRepository

ProductNotFoundError
CartError
```

The Domain should contain business concepts without knowing how the outside world communicates with the application.

---

## Application

Contains application-specific workflows and Use Cases.

Examples:

```text
GetProductUseCase
SearchProductsUseCase
AddProductToCartUseCase
GetCartUseCase
```

A Use Case coordinates domain abstractions.

It should not directly call an external API.

### Bad

```python
class GetProductUseCase:

    def execute(self, product_id: int):
        requests.get(
            f"https://external-api/products/{product_id}"
        )
```

The Use Case now knows:

- HTTP exists
- which HTTP library is used
- which external provider is used
- how the provider URL works

### Preferred

```python
class GetProductUseCase:

    def __init__(self, repository: ProductRepository):
        self.repository = repository

    def execute(self, product_id: int) -> Product:
        return self.repository.get_by_id(product_id)
```

Now the Use Case only knows the contract:

```text
ProductRepository
```

---

## Infrastructure

Contains implementations for external technical concerns.

Examples:

```text
HTTP clients
REST APIs
repositories
databases
cache
environment configuration
external service adapters
```

For example:

```text
ProductRepository
        ^
        |
DummyJsonProductRepository
```

Infrastructure implements Domain abstractions.

The Domain does not depend on Infrastructure.

---

## Presentation

Contains MCP-specific entry points.

Examples:

```text
FastMCP Server
MCP Tools
MCP Resources
MCP Prompts
MCP Apps
```

An MCP Tool should remain thin.

Its responsibility is primarily:

```text
MCP Request
     |
     v
Validate / map input
     |
     v
Use Case
     |
     v
Map result
     |
     v
MCP Response
```

Business logic should not live inside MCP decorators.

---

# MCP Architecture

MCP and FastMCP are different concepts.

```text
MCP
 |
 `-- Protocol


FastMCP
 |
 `-- Python framework implementing MCP
```

The application uses MCP over **Streamable HTTP**.

```text
MCP Host
   |
   | Streamable HTTP
   v
http://localhost:8000/mcp
   |
   v
FastMCP Server
```

The server is configured to run stateless HTTP by default.

---

# MCP Components

## Tools

Actions the model can execute.

Examples:

```text
get_product
search_products
add_product_to_cart
get_cart
remove_product_from_cart
```

Conceptually:

```text
LLM
 |
 | tool call
 v
MCP Tool
 |
 v
Use Case
```

---

## Resources

Resources expose data or context that an MCP Host can read.

They should not become a replacement for application business logic.

---

## Prompts

Prompts provide reusable prompt templates through MCP.

They belong to the MCP / Presentation boundary.

---

# MCP App UI

MCP Apps allow compatible MCP hosts to display interactive UI associated with MCP functionality.

Our e-commerce example will eventually render something conceptually similar to:

```text
+--------------------------------+
| Product                        |
|                                |
| Smartphone                     |
|                                |
| $799.99                        |
|                                |
|       [ Add to cart ]          |
+---------------+----------------+
                |
                v
          MCP Tool Call
                |
                v
     AddProductToCartUseCase
                |
                v
          CartRepository
```

The important architectural rule is:

> MCP App UI is a Presentation concern.

The UI should not implement business rules.

For example, clicking:

```text
[ Add to cart ]
```

should result in:

```text
MCP App UI
     |
     v
MCP Tool
     |
     v
AddProductToCartUseCase
     |
     v
CartRepository
```

The UI does not manipulate infrastructure directly.

---

# Environment Configuration

Runtime configuration must come from environment variables rather than being hardcoded.

Current variables:

```text
MCP_SERVER_TRANSPORT
MCP_SERVER_HOST
MCP_SERVER_PORT
MCP_STATELESS_HTTP
```

Example:

```powershell
$env:MCP_SERVER_PORT="9000"
```

The configuration flow is:

```text
Operating System / Container
           |
           | Environment Variables
           v
EnvironmentSettings
           |
           v
server.py
           |
           v
FastMCP
```

This allows the same application code to run in:

```text
Local
Development
Test
Staging
Production
Docker
Kubernetes
Cloud environments
```

with different configuration.

Secrets must never be committed to Git.

---

# Python Package Conventions

`__init__.py` can be used to define the public API of a Python package.

For example:

```python
from infrastructure.config.environment import EnvironmentSettings

__all__ = [
    "EnvironmentSettings",
]
```

Consumers can then use:

```python
from infrastructure.config import EnvironmentSettings
```

instead of:

```python
from infrastructure.config.environment import EnvironmentSettings
```

This reduces coupling to the internal file structure.

Conceptually, this is similar to a TypeScript:

```text
index.ts
```

used as a barrel export.

`__all__` defines the intended public API.

It is **not** an access modifier like `public` or `private` in C#.

---

# Python / C# Reference

This project is also designed to help .NET developers learn Python.

| Python               | C# concept                                                                |
| -------------------- | ------------------------------------------------------------------------- |
| `str`                | `string`                                                                  |
| `int`                | `int`                                                                     |
| `float`              | `double`                                                                  |
| `bool`               | `bool`                                                                    |
| `None`               | `null`                                                                    |
| `list[T]`            | `List<T>`                                                                 |
| `dict[K, V]`         | `Dictionary<K, V>`                                                        |
| `tuple[T1, T2]`      | roughly `(T1, T2)` / tuple                                                |
| `self`               | `this`                                                                    |
| `ABC`                | `abstract class`                                                          |
| `@abstractmethod`    | `abstract method`                                                         |
| Repository `ABC`     | often used similarly to `IRepository`                                     |
| `Product \| None`    | approximately `Product?`                                                  |
| `Exception`          | `Exception`                                                               |
| `raise`              | `throw`                                                                   |
| `try / except`       | `try / catch`                                                             |
| `__init__`           | constructor                                                               |
| `__init__.py`        | package initialization / similar purpose to barrel exports                |
| Pydantic `BaseModel` | typed model + validation/serialization                                    |
| `@decorator`         | conceptually similar to attributes/middleware behavior depending on usage |

When new Python concepts are introduced, their C# equivalents should be documented when useful.

---

# Domain Models

Structured models use Pydantic where validation and serialization are useful.

Example:

```python
from typing import Annotated

from pydantic import BaseModel


class Product(BaseModel):
    id: Annotated[int, "Product identifier"]
    title: Annotated[str, "Product title"]
    description: Annotated[str, "Product description"]
    price: Annotated[float, "Product price"]
    thumbnail: Annotated[str, "Product thumbnail URL"]
```

Pydantic provides:

```text
validation
type coercion
serialization
JSON-compatible output
JSON Schema generation
```

---

# Repository Pattern

Repositories represent abstractions over data or external systems.

Example:

```python
from abc import ABC, abstractmethod

from domain.entities import Product


class ProductRepository(ABC):

    @abstractmethod
    def get_by_id(self, product_id: int) -> Product:
        pass
```

For a C# developer, this is conceptually similar to:

```csharp
public interface IProductRepository
{
    Product GetById(int productId);
}
```

A concrete Infrastructure implementation can then provide the actual behavior:

```text
ProductRepository
        ^
        |
DummyJsonProductRepository
```

---

# External APIs

External APIs must be accessed from Infrastructure.

The initial implementation uses the public **DummyJSON API** for the e-commerce example.

The architecture prevents application use cases from depending directly on DummyJSON.

```text
Application
    |
    v
ProductRepository
    ^
    |
Infrastructure implementation
    |
    v
DummyJSON
```

This allows the external provider to be replaced later without rewriting the Application or Domain layers.

---

# Error Handling Strategy

The project uses a centralized exception hierarchy inspired by Clean Architecture and common .NET exception-handling patterns.

The goal is to distinguish:

```text
expected business failures
          vs
technical/infrastructure failures
```

while providing a common structured error contract.

---

## Error Hierarchy

```text
AppError
|
|-- DomainError
|   |
|   |-- ProductNotFoundError
|   `-- CartError
|
|-- ValidationError
|
`-- InfrastructureError
    |
    |-- ExternalAPIError
    `-- ExternalAPITimeoutError
```

All known application errors ultimately derive from:

```text
AppError
```

---

## Base Application Error

```python
from typing import Any


class AppError(Exception):
    error_code: str = "UNKNOWN_ERROR"

    def __init__(
        self,
        message: str,
        details: dict[str, Any] | None = None,
    ):
        self.message = message
        self.details = details or {}

        super().__init__(message)

    def to_dict(self) -> dict:
        return {
            "error_code": self.error_code,
            "error_type": self.__class__.__name__,
            "message": self.message,
            "details": self.details,
        }
```

Conceptually, this is similar to C#:

```csharp
public abstract class AppException : Exception
{
    public string ErrorCode { get; }

    protected AppException(
        string message,
        string errorCode)
        : base(message)
    {
        ErrorCode = errorCode;
    }
}
```

---

## Domain Errors

Domain errors represent expected business failures.

Examples:

```text
Product does not exist
Cart is empty
Product cannot be added to the cart
Requested quantity violates a business rule
```

Example:

```python
class DomainError(AppError):
    error_code = "DOMAIN_ERROR"


class ProductNotFoundError(DomainError):
    error_code = "PRODUCT_NOT_FOUND"

    def __init__(self, product_id: int):
        super().__init__(
            message=f"Product '{product_id}' was not found.",
            details={
                "product_id": product_id,
            },
        )
```

Conceptually similar to:

```csharp
public class ProductNotFoundException : DomainException
{
    public int ProductId { get; }

    public ProductNotFoundException(int productId)
        : base($"Product '{productId}' was not found.")
    {
        ProductId = productId;
    }
}
```

---

## Validation Errors

Validation errors represent invalid application input or violated constraints.

Examples:

```text
Invalid product ID
Quantity must be greater than zero
Missing required input
Invalid cart operation
```

These are expected failures.

They should provide enough structured information for the MCP Host or LLM to understand what needs to be corrected.

---

## Infrastructure Errors

Infrastructure errors represent failures involving technical dependencies.

Examples:

```text
External API unavailable
HTTP timeout
Connection failure
Unexpected downstream response
Database unavailable
```

For example:

```python
class InfrastructureError(AppError):
    error_code = "INFRASTRUCTURE_ERROR"


class ExternalAPIError(InfrastructureError):
    error_code = "EXTERNAL_API_ERROR"
```

The Domain must not depend on Infrastructure exceptions.

Raw library exceptions should not leak through the entire application.

For example:

```text
httpx.TimeoutException
        |
        v
ExternalAPITimeoutError
        |
        v
Application / Presentation
```

instead of:

```text
httpx.TimeoutException
        |
        +---------------------> MCP Host
```

---

# Error Translation

Infrastructure is responsible for translating low-level technical failures when appropriate.

For example:

```text
HTTP 404 from product provider
          |
          v
ProductNotFoundError


HTTP timeout
          |
          v
ExternalAPITimeoutError


HTTP 500
          |
          v
ExternalAPIError
```

This prevents the rest of the application from becoming coupled to a particular HTTP library.

---

# Presentation Error Boundary

MCP Tools should not contain duplicated error handling.

Avoid:

```python
@mcp.tool
def tool_one():
    try:
        ...
    except AppError:
        ...


@mcp.tool
def tool_two():
    try:
        ...
    except AppError:
        ...


@mcp.tool
def tool_three():
    try:
        ...
    except AppError:
        ...
```

The desired architecture is:

```text
MCP Host
   |
   v
Presentation Error Boundary
   |
   v
MCP Tool
   |
   v
Use Case
   |
   v
Domain / Repository
```

Known application errors can be converted into structured MCP-friendly errors.

Unexpected exceptions should be:

```text
logged
   |
   v
converted to generic internal error
   |
   v
returned without sensitive details
```

This is conceptually similar to ASP.NET Core:

```text
Python / MCP                 ASP.NET Core

AppError                     AppException
DomainError                  DomainException
InfrastructureError          InfrastructureException
central error boundary       IExceptionHandler / Middleware
raise                        throw
except                       catch
```

---

# Structured Errors

Errors should contain structured information when useful.

Example:

```json
{
  "error_code": "PRODUCT_NOT_FOUND",
  "error_type": "ProductNotFoundError",
  "message": "Product '123' was not found.",
  "details": {
    "product_id": 123
  }
}
```

Structured errors improve:

- MCP client behavior
- LLM reasoning
- logging
- observability
- automated tests
- debugging

---

# Error Handling Rules

1. Do not expose raw infrastructure exceptions directly to MCP clients.
2. Do not duplicate `try/except` blocks across every MCP Tool.
3. Use specific Domain errors for expected business failures.
4. Use Validation errors for invalid input and violated constraints.
5. Translate external technical failures into application-specific errors.
6. Preserve useful structured context through `details`.
7. Log unexpected exceptions at the application boundary.
8. Never expose secrets, tokens, stack traces, or sensitive infrastructure details to MCP clients.
9. Keep error codes stable so clients and automated tests can rely on them.
10. Presentation is responsible for translating application errors into MCP-friendly responses.

---

# Dependency Injection and Composition

Dependencies should be explicit.

For example:

```text
DummyJsonProductRepository
            |
            v
GetProductUseCase
            |
            v
MCP Tool
```

The composition/root wiring belongs near the application entry point, not inside the Domain.

The project should avoid hidden global dependencies when practical.

This will be introduced incrementally as the application grows.

---

# Testing Strategy

The architecture should allow business behavior to be tested without:

```text
starting FastMCP
calling DummyJSON
opening an HTTP port
running MCP App UI
```

For example:

```text
Unit Test
   |
   v
GetProductUseCase
   |
   v
FakeProductRepository
```

This makes the Use Case independently testable.

---

## Unit Tests

Unit tests should focus on:

```text
Domain behavior
Use Cases
Validation
Error handling
```

using fake or mock dependencies.

---

## Integration Tests

Integration tests can validate boundaries separately:

```text
Infrastructure
      |
      v
DummyJSON API
```

and:

```text
MCP Client
    |
    v
FastMCP Server
```

This separation prevents external API behavior from making every business test unreliable.

---

# Development Setup

Requirements:

```text
Python 3.12+
uv
```

Install/synchronize dependencies:

```bash
uv sync
```

Run the MCP server:

```bash
uv run python -m presentation.mcp.server
```

Default endpoint:

```text
http://localhost:8000/mcp
```

---

# Virtual Environment

The project uses:

```text
.venv/
```

for isolated Python dependencies.

`uv` manages the project environment automatically.

Commands should generally be executed using:

```bash
uv run ...
```

For example:

```bash
uv run python --version
```

This avoids relying on globally installed project dependencies.

---

# Development Principles

When extending this template:

1. Keep MCP-specific code in Presentation.
2. Keep business workflows in Application.
3. Keep business models and contracts independent from frameworks where practical.
4. Keep external integrations in Infrastructure.
5. Depend on abstractions instead of concrete Infrastructure implementations.
6. Keep MCP Tools thin.
7. Do not hardcode environment-specific configuration.
8. Do not commit secrets.
9. Prefer typed Python.
10. Validate external data at system boundaries.
11. Keep external API DTOs separate from Domain models when their structures diverge.
12. Make Use Cases independently testable.
13. Prefer explicit dependencies over hidden global state.
14. Add abstractions when they solve a real architectural problem.
15. Keep the Domain independent from FastMCP.
16. Translate Infrastructure failures before exposing them outside their boundary.
17. Use stable structured error codes.
18. Keep MCP App UI focused on presentation and interaction.
19. Do not put business logic inside MCP decorators.
20. Keep the external API replaceable.

---

# Planned Learning Flow

The template is being built incrementally.

```text
FastMCP Server
      |
      v
HTTP Transport
      |
      v
Environment Configuration
      |
      v
Python Package Structure
      |
      v
Pydantic Models
      |
      v
Domain Entities
      |
      v
Repository Contracts
      |
      v
Error Hierarchy
      |
      v
Infrastructure / External API
      |
      v
Application Use Cases
      |
      v
MCP Tools
      |
      v
Dependency Composition
      |
      v
Centralized Error Handling
      |
      v
MCP Resources
      |
      v
MCP Prompts
      |
      v
MCP App UI
      |
      v
Interactive MCP Actions
      |
      v
Unit Tests
      |
      v
Integration Tests
      |
      v
Claude / Copilot integration
```

---

# Final Target

The final project should demonstrate the complete flow:

```text
Claude / Copilot
       |
       | MCP over HTTP
       v
FastMCP Server
       |
       v
MCP App UI
       |
       | user action
       v
MCP Tool
       |
       v
Application Use Case
       |
       v
Domain Contract
       |
       v
Infrastructure Adapter
       |
       | HTTP
       v
External Service
```

with errors flowing safely in the opposite direction:

```text
External failure
       |
       v
Infrastructure Error
       |
       v
Application / Domain Error
       |
       v
Presentation Error Boundary
       |
       v
Structured MCP Error
       |
       v
Claude / Copilot
```

---

# Purpose

This repository is intended to become a reusable **template and learning reference for creating production-quality FastMCP servers and MCP Apps using Clean Architecture**.

The project demonstrates how MCP can be treated as an application boundary rather than allowing MCP-specific concerns to spread throughout the codebase.

The core business logic should remain independent from:

```text
FastMCP
MCP transport
MCP App UI
Claude
Copilot
HTTP providers
databases
external APIs
```

This makes the application easier to:

```text
maintain
test
extend
replace integrations
run in different environments
connect to different MCP hosts
```

while preserving clear architectural boundaries.

Maintenance

ActivityMaintained
ResponsivenessNo issues