Skip to main content
Glama
renaisanci

mcp-clean-architecture

by renaisanci

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.

                         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:

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

Dependencies point toward the core application.

The Domain must never depend on:

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

For example:

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:

DummyJSON

to later be replaced with:

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:

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

Related MCP server: NitroStack

Domain

Contains the core business concepts and contracts.

Examples:

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:

GetProductUseCase
SearchProductsUseCase
AddProductToCartUseCase
GetCartUseCase

A Use Case coordinates domain abstractions.

It should not directly call an external API.

Bad

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

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:

ProductRepository

Infrastructure

Contains implementations for external technical concerns.

Examples:

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

For example:

ProductRepository
        ^
        |
DummyJsonProductRepository

Infrastructure implements Domain abstractions.

The Domain does not depend on Infrastructure.


Presentation

Contains MCP-specific entry points.

Examples:

FastMCP Server
MCP Tools
MCP Resources
MCP Prompts
MCP Apps

An MCP Tool should remain thin.

Its responsibility is primarily:

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.

MCP
 |
 `-- Protocol


FastMCP
 |
 `-- Python framework implementing MCP

The application uses MCP over Streamable HTTP.

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:

get_product
search_products
add_product_to_cart
get_cart
remove_product_from_cart

Conceptually:

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:

+--------------------------------+
| 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:

[ Add to cart ]

should result in:

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:

MCP_SERVER_TRANSPORT
MCP_SERVER_HOST
MCP_SERVER_PORT
MCP_STATELESS_HTTP

Example:

$env:MCP_SERVER_PORT="9000"

The configuration flow is:

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

This allows the same application code to run in:

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:

from infrastructure.config.environment import EnvironmentSettings

__all__ = [
    "EnvironmentSettings",
]

Consumers can then use:

from infrastructure.config import EnvironmentSettings

instead of:

from infrastructure.config.environment import EnvironmentSettings

This reduces coupling to the internal file structure.

Conceptually, this is similar to a TypeScript:

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:

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:

validation
type coercion
serialization
JSON-compatible output
JSON Schema generation

Repository Pattern

Repositories represent abstractions over data or external systems.

Example:

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:

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

A concrete Infrastructure implementation can then provide the actual behavior:

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.

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:

expected business failures
          vs
technical/infrastructure failures

while providing a common structured error contract.


Error Hierarchy

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

All known application errors ultimately derive from:

AppError

Base Application Error

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#:

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:

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

Example:

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:

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:

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:

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

For example:

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:

httpx.TimeoutException
        |
        v
ExternalAPITimeoutError
        |
        v
Application / Presentation

instead of:

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

Error Translation

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

For example:

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:

@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:

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:

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

This is conceptually similar to ASP.NET Core:

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:

{
  "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:

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:

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

For example:

Unit Test
   |
   v
GetProductUseCase
   |
   v
FakeProductRepository

This makes the Use Case independently testable.


Unit Tests

Unit tests should focus on:

Domain behavior
Use Cases
Validation
Error handling

using fake or mock dependencies.


Integration Tests

Integration tests can validate boundaries separately:

Infrastructure
      |
      v
DummyJSON API

and:

MCP Client
    |
    v
FastMCP Server

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


Development Setup

Requirements:

Python 3.12+
uv

Install/synchronize dependencies:

uv sync

Run the MCP server:

uv run python -m presentation.mcp.server

Default endpoint:

http://localhost:8000/mcp

Virtual Environment

The project uses:

.venv/

for isolated Python dependencies.

uv manages the project environment automatically.

Commands should generally be executed using:

uv run ...

For example:

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.

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:

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:

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:

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

This makes the application easier to:

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

while preserving clear architectural boundaries.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol server that enables AI assistants to interact with a complete e-commerce application, providing authentication, product browsing, and shopping cart management through standardized MCP tools.
  • F
    license
    Not graded
    quality
    B
    maintenance
    A Python framework for building MCP servers with modular architecture, dependency injection, and built-in authentication. Enables creating scalable, testable MCP services with features like pipeline interceptors and background tasks.
    3
  • A
    license
    Not graded
    quality
    C
    maintenance
    A production-ready template for developing Model Context Protocol (MCP) servers using Python and FastMCP.
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • FastMCP commerce server starter: product catalog, search, and checkout. Deploy to Vercel in 5 min.

  • Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.

  • MCP Server for Slima - AI Writing IDE for Novel Authors with AI Beta Reader.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/renaisanci/mcp-clean-architecture'

If you have feedback or need assistance with the MCP directory API, please join our Discord server