Skip to main content
Glama
renaisanci

mcp-clean-architecture

by renaisanci

FastMCP 整洁架构 — MCP 应用 UI 模板

一个面向生产的模板,用于使用 Python 和 FastMCP 构建 MCP 服务器和 MCP 应用,遵循整洁架构、依赖倒置、关注点分离以及现代 Python 实践。

该项目也旨在为来自 C# / .NET 的开发者提供学习参考。

目标不仅是构建一个可运行的 MCP 服务器,而是构建一个保持可维护性、可测试性、可扩展性,并且独立于外部框架和服务的服务器。


目标

此模板演示了如何使用以下技术构建 MCP 应用:

  • Python

  • FastMCP

  • Streamable HTTP 传输

  • 无状态 HTTP

  • MCP 工具

  • MCP 资源

  • MCP 提示词

  • MCP 应用 / 应用 UI

  • 整洁架构

  • 依赖倒置

  • 仓储模式

  • 用例

  • Pydantic 模型

  • 外部 REST API 集成

  • 基于环境的配置

  • 异步 HTTP 通信

  • 依赖注入 / 组合

  • 集中式错误处理

  • 结构化应用错误

  • 日志记录

  • 单元测试

  • 集成测试

示例领域是一个电子商务应用

产品从公共外部 API 获取,并通过 MCP 暴露。

该应用将逐步演进以支持以下操作:

  • 搜索产品

  • 查看产品详情

  • 将产品添加到购物车

  • 查看购物车

  • 从购物车中移除产品

MCP 应用 UI 将在兼容的 MCP 主机内提供交互式体验。


架构

该项目遵循整洁架构原则。

                         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

依赖规则

最重要的规则是:

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

依赖指向核心应用。

领域层绝不能依赖

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

例如:

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

GetProductUseCase 知道 ProductRepository 抽象。

不知道产品是通过 HTTP 或 DummyJSON 获取的。

这允许:

DummyJSON

之后被替换为:

SQL Server
PostgreSQL
MongoDB
another REST API
mock repository

而无需更改应用用例。


项目结构

项目将逐步演进为以下结构:

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

文件夹应在具有实际职责时引入。

模板不应仅仅为了拥有更多层而创建抽象。


层职责

Related MCP server: NitroStack

领域层

包含核心业务概念和契约。

示例:

Product
Cart

ProductRepository
CartRepository

ProductNotFoundError
CartError

领域层应包含业务概念,而不需要知道外部世界如何与应用通信。


应用层

包含应用特定的工作流和用例。

示例:

GetProductUseCase
SearchProductsUseCase
AddProductToCartUseCase
GetCartUseCase

用例协调领域抽象。

它不应直接调用外部 API。

不好的做法

class GetProductUseCase:

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

用例现在知道了:

  • HTTP 存在

  • 使用了哪个 HTTP 库

  • 使用了哪个外部提供商

  • 提供商 URL 如何工作

推荐做法

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)

现在用例只知道契约:

ProductRepository

基础设施层

包含外部技术关注点的实现。

示例:

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

例如:

ProductRepository
        ^
        |
DummyJsonProductRepository

基础设施实现领域抽象。

领域层不依赖基础设施层。


表示层

包含 MCP 特定的入口点。

示例:

FastMCP Server
MCP Tools
MCP Resources
MCP Prompts
MCP Apps

MCP 工具应保持精简。

其职责主要是:

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

业务逻辑不应存在于 MCP 装饰器内部。


MCP 架构

MCP 和 FastMCP 是不同的概念。

MCP
 |
 `-- Protocol


FastMCP
 |
 `-- Python framework implementing MCP

应用通过 Streamable HTTP 使用 MCP。

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

服务器默认配置为运行无状态 HTTP。


MCP 组件

工具

模型可以执行的操作。

示例:

get_product
search_products
add_product_to_cart
get_cart
remove_product_from_cart

概念上:

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

资源

资源暴露 MCP 主机可以读取的数据或上下文。

它们不应成为应用业务逻辑的替代品。


提示词

提示词通过 MCP 提供可复用的提示词模板。

它们属于 MCP / 表示层边界。


MCP 应用 UI

MCP 应用允许兼容的 MCP 主机显示与 MCP 功能关联的交互式 UI。

我们的电子商务示例最终将渲染概念上类似于以下内容的东西:

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

重要的架构规则是:

MCP 应用 UI 是表示层关注点。

UI 不应实现业务规则。

例如,点击:

[ Add to cart ]

应导致:

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

UI 不直接操作基础设施。


环境配置

运行时配置必须来自环境变量,而不是硬编码。

当前变量:

MCP_SERVER_TRANSPORT
MCP_SERVER_HOST
MCP_SERVER_PORT
MCP_STATELESS_HTTP

示例:

$env:MCP_SERVER_PORT="9000"

配置流程为:

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

这允许相同的应用代码在以下环境中运行:

Local
Development
Test
Staging
Production
Docker
Kubernetes
Cloud environments

并使用不同的配置。

机密信息绝不能提交到 Git。


Python 包约定

__init__.py 可用于定义 Python 包的公共 API。

例如:

from infrastructure.config.environment import EnvironmentSettings

__all__ = [
    "EnvironmentSettings",
]

使用者随后可以使用:

from infrastructure.config import EnvironmentSettings

而不是:

from infrastructure.config.environment import EnvironmentSettings

这减少了对内部文件结构的耦合。

概念上,这类似于 TypeScript 中的:

index.ts

用作桶导出(barrel export)。

__all__ 定义了预期的公共 API。

不是像 C# 中 publicprivate 那样的访问修饰符。


Python / C# 参考

该项目也旨在帮助 .NET 开发者学习 Python。

Python

C# 概念

str

string

int

int

float

double

bool

bool

None

null

list[T]

List<T>

dict[K, V]

Dictionary<K, V>

tuple[T1, T2]

大致相当于 (T1, T2) / 元组

self

this

ABC

abstract class

@abstractmethod

abstract method

仓储 ABC

通常类似于 IRepository 的用法

Product | None

近似于 Product?

Exception

Exception

raise

throw

try / except

try / catch

__init__

构造函数

__init__.py

包初始化 / 与桶导出目的类似

Pydantic BaseModel

类型化模型 + 验证/序列化

@decorator

概念上类似于特性/中间件行为,具体取决于用法

当引入新的 Python 概念时,应在有用时记录其 C# 对应概念。


领域模型

在验证和序列化有用的情况下,结构化模型使用 Pydantic。

示例:

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 提供:

validation
type coercion
serialization
JSON-compatible output
JSON Schema generation

仓储模式

仓储代表对数据或外部系统的抽象。

示例:

from abc import ABC, abstractmethod

from domain.entities import Product


class ProductRepository(ABC):

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

对于 C# 开发者来说,这在概念上类似于:

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

具体的基础设施实现随后可以提供实际行为:

ProductRepository
        ^
        |
DummyJsonProductRepository

外部 API

外部 API 必须从基础设施层访问。

初始实现使用公共 DummyJSON API 作为电子商务示例。

架构防止应用用例直接依赖 DummyJSON。

Application
    |
    v
ProductRepository
    ^
    |
Infrastructure implementation
    |
    v
DummyJSON

这允许以后替换外部提供商,而无需重写应用层或领域层。


错误处理策略

项目使用受整洁架构和常见 .NET 异常处理模式启发的集中式异常层次结构。

目标是区分:

expected business failures
          vs
technical/infrastructure failures

同时提供统一的结构化错误契约。


错误层次结构

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

所有已知的应用错误最终都派生自:

AppError

基础应用错误

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,
        }

概念上,这类似于 C#:

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

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

领域错误

领域错误表示预期的业务失败。

示例:

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

示例:

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,
            },
        )

概念上类似于:

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

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

验证错误

验证错误表示无效的应用输入或违反的约束。

示例:

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

这些是预期的失败。

它们应提供足够的结构化信息,使 MCP 主机或 LLM 能够理解需要纠正什么。


基础设施错误

基础设施错误表示涉及技术依赖的失败。

示例:

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

例如:

class InfrastructureError(AppError):
    error_code = "INFRASTRUCTURE_ERROR"


class ExternalAPIError(InfrastructureError):
    error_code = "EXTERNAL_API_ERROR"

领域层绝不能依赖基础设施异常。

原始库异常不应泄漏到整个应用。

例如:

httpx.TimeoutException
        |
        v
ExternalAPITimeoutError
        |
        v
Application / Presentation

而不是:

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

错误转换

基础设施负责在适当时转换低层技术失败。

例如:

HTTP 404 from product provider
          |
          v
ProductNotFoundError


HTTP timeout
          |
          v
ExternalAPITimeoutError


HTTP 500
          |
          v
ExternalAPIError

这防止应用的其余部分与特定 HTTP 库耦合。


表示层错误边界

MCP 工具不应包含重复的错误处理。

避免:

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


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


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

期望的架构是:

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

已知的应用错误可以转换为结构化的 MCP 友好错误。

意外异常应:

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

这在概念上类似于 ASP.NET Core:

Python / MCP                 ASP.NET Core

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

结构化错误

错误在有用时应包含结构化信息。

示例:

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

结构化错误改善了:

  • MCP 客户端行为

  • LLM 推理

  • 日志记录

  • 可观测性

  • 自动化测试

  • 调试


错误处理规则

  1. 不要直接将原始基础设施异常暴露给 MCP 客户端。

  2. 不要在每一个 MCP 工具中重复 try/except 块。

  3. 对预期的业务失败使用特定的领域错误。

  4. 对无效输入和违反的约束使用验证错误。

  5. 将外部技术失败转换为应用特定错误。

  6. 通过 details 保留有用的结构化上下文。

  7. 在应用边界记录意外异常。

  8. 绝不向 MCP 客户端暴露机密、令牌、堆栈跟踪或敏感的基础设施细节。

  9. 保持错误代码稳定,以便客户端和自动化测试可以依赖它们。

  10. 表示层负责将应用错误转换为 MCP 友好的响应。


依赖注入与组合

依赖关系应显式化。

例如:

DummyJsonProductRepository
            |
            v
GetProductUseCase
            |
            v
MCP Tool

组合/根接线属于应用入口点附近,而不是领域层内部。

项目应在实际可行时避免隐藏的全局依赖。

这将随着应用的增长逐步引入。


测试策略

架构应允许在不依赖以下条件的情况下测试业务行为:

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

例如:

Unit Test
   |
   v
GetProductUseCase
   |
   v
FakeProductRepository

这使得用例可以独立测试。


单元测试

单元测试应专注于:

Domain behavior
Use Cases
Validation
Error handling

使用假(fake)或模拟(mock)依赖。


集成测试

集成测试可以分别验证边界:

Infrastructure
      |
      v
DummyJSON API

以及:

MCP Client
    |
    v
FastMCP Server

这种分离防止外部 API 行为使每个业务测试变得不可靠。


开发环境设置

要求:

Python 3.12+
uv

安装/同步依赖:

uv sync

运行 MCP 服务器:

uv run python -m presentation.mcp.server

默认端点:

http://localhost:8000/mcp

虚拟环境

项目使用:

.venv/

用于隔离的 Python 依赖。

uv 自动管理项目环境。

命令通常应使用以下方式执行:

uv run ...

例如:

uv run python --version

这可以避免依赖全局安装的项目依赖。


开发原则

在扩展此模板时:

  1. 将 MCP 特定代码保留在 Presentation 中。

  2. 将业务工作流保留在 Application 中。

  3. 在可行的情况下,保持业务模型和契约独立于框架。

  4. 将外部集成保留在 Infrastructure 中。

  5. 依赖抽象而不是具体的 Infrastructure 实现。

  6. 保持 MCP Tools 轻量。

  7. 不要硬编码特定于环境的配置。

  8. 不要提交机密信息。

  9. 优先使用类型化 Python。

  10. 在系统边界验证外部数据。

  11. 当外部 API DTO 与领域模型结构不同时,将其分开。

  12. 使用例可独立测试。

  13. 优先使用显式依赖,而不是隐藏的全局状态。

  14. 在解决实际架构问题时添加抽象。

  15. 保持 Domain 独立于 FastMCP。

  16. 在将 Infrastructure 故障暴露到其边界之外之前进行转换。

  17. 使用稳定的结构化错误代码。

  18. 保持 MCP App UI 专注于表示和交互。

  19. 不要将业务逻辑放在 MCP 装饰器中。

  20. 保持外部 API 可替换。


计划的学习流程

该模板正在增量构建中。

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

最终目标

最终项目应演示完整流程:

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

错误则安全地沿相反方向流动:

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

目的

本仓库旨在成为一个可复用的模板和学习参考,用于使用 Clean Architecture 创建生产级 FastMCP 服务器和 MCP 应用

该项目演示了如何将 MCP 视为应用程序边界,而不是让 MCP 特定的关注点扩散到整个代码库。

核心业务逻辑应保持独立于:

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

这使应用程序更易于:

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

同时保持清晰的架构边界。

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