Skip to main content
Glama

dd-arch

DDD 架构 · MCP 工具管理平台。

快速启动

启动 MySQL 8.0(首次创建数据卷时会执行 docs/init.sql):

docker compose -f docs/docker-compose.yml up -d

安装后端依赖:

uv sync --extra dev

安装并构建前端:

cd frontend
npm install
npm run build
cd ..

启动 HTTP + MCP Streamable HTTP:

source .venv/bin/activate
python -m dd_arch.app.application

如果没有激活虚拟环境,也可以直接通过 uv 调用:

uv run python -m dd_arch.app.application

启动后主要入口:

/api       管理端 REST API
/mcp       MCP Streamable HTTP
/          frontend/dist 静态前端

停止 MySQL:

docker compose -f docs/docker-compose.yml down

如需重新执行初始化 SQL,先删除数据卷:

docker compose -f docs/docker-compose.yml down -v
docker compose -f docs/docker-compose.yml up -d

⚠️ 安全前提(RCE):本平台允许从 Web 提交并在服务器进程内执行任意 Python 代码,本质是远程代码执行。当前采用进程内 exec(最简,完全 RCE,无沙箱)仅适用于受信任的内网/单用户环境。编辑类接口必须置于受信任网络或加鉴权后再对外。命名空间预置的最小 __builtins__ 不构成安全边界;沙箱隔离是预留扩展点(IToolExecutorPort 可替换为受限/子进程实现)。

权威规格见 spec.md,执行进度见 progress.md

Related MCP server: dynamic-mcp

架构总览

项目采用轻量六边形架构,并额外保留通用类型层和启动层。工具来自数据库,不再有静态注册中心或工具目录。

                 +-------------+
                 |   trigger   |  HTTP / MCP 入站适配
                 +------+------+
                        |
                 +------v------+
                 |     api     |  DTO / 响应契约
                 +------+------+
                        |
                 +------v------+
                 |   domain    |  领域模型 / 领域服务 / 接口
                 +------^------+
                        |
        +---------------+---------------+
        |        infrastructure         |  DB / Cache / Event / Metrics / Executor
        +-------------------------------+

types 提供跨层共享的错误码、异常、响应和分页类型。
app 作为组合根装配所有层,并负责生命周期。

核心约束:

  • domain 不依赖 FastAPI、SQLAlchemy、MCP、infrastructure 等外部技术;也不真正 exec 代码(编译执行是 IToolExecutorPort 的 infra 职责,domain 只定义接口)。

  • 简单编排直接放在 domain service 中;trigger 只做协议适配、参数接收、响应包装。

  • infrastructure 实现领域层定义的 repository/port 接口,不反向依赖 trigger

  • app 是唯一知道所有层实现细节的组合根,负责依赖注入、生命周期和挂载。

  • 数据库、repository、service 主链路使用同步调用;FastAPI/MCP 入口保留 async 协议函数,并通过线程池调用同步服务。

项目结构

.
├── resources/
│   └── application.yaml          # 默认运行配置
├── docs/init.sql                 # MySQL schema
├── pyproject.toml                # 项目依赖和构建配置
├── docs/spec.md                  # 项目权威规格
├── docs/progress.md              # 执行进度
├── docs/step-*.md                # 分步骤规格
├── tests/                        # 自动化测试
├── frontend/                     # Vue 管理端(含工具编辑器)
└── src/dd_arch/
    ├── types/                    # 通用基础类型
    ├── api/                      # HTTP/API 契约
    ├── domain/                   # 领域核心(tooldef / invocation)
    ├── infrastructure/           # 外部技术实现(含 ToolExecutorPort)
    ├── trigger/                  # HTTP/MCP 入站适配器
    └── app/                      # 组合根、启动入口和运行配置加载

分层职责

types/ 通用类型层

这一层放跨层共享且不携带业务流程的基础类型,主要包括:

  • 统一错误码。

  • 工具异常和业务异常。

  • HTTP 统一响应信封 {code, info, data}

  • 内部分页结果。

它可以被各层引用,但不主动依赖业务层或基础设施层。

api/ 契约层

这一层描述管理端 API 的输入输出契约,主要包括:

  • 工具定义、服务状态、调用日志、日志统计等 DTO。

  • API 错误响应构造工具。

api 层不处理业务流程,只定义 trigger 和 service 之间稳定的数据形状。

domain/ 领域层

领域层是业务核心,不包含框架代码和数据库代码。

domain/
├── tooldef/                      # 工具定义子域(Web 动态工具核心)
│   ├── model/                    # ToolDefinitionAggregate / 参数与状态值对象 / 编译产物 VO
│   ├── adapter/repository/       # 工具定义、工具配置仓储接口
│   ├── adapter/port/             # 代码执行器端口接口(IToolExecutorPort)
│   └── service/                  # 参数→JSON Schema、参数矫正等领域规则
└── invocation/                   # 工具调用子域
    ├── model/                    # 调用日志、调用事件、状态值对象
    ├── adapter/repository/       # 调用日志仓储接口
    ├── adapter/port/             # 事件发布、指标等端口接口
    └── service/                  # 调用日志和统计领域规则

这一层解决的问题:

  • 一个动态工具如何表达:名称、描述、代码串、是否异步、tags、参数列表、状态(DRAFT/ENABLED/DISABLED)。

  • 参数列表如何生成 JSON Schema,原始入参如何按类型矫正。

  • 每个工具的运行时配置如何表达。

  • 工具调用成功、失败、耗时、错误如何记录为领域事实。

  • 调用事件如何作为领域事件发布给外部处理器。

  • repository/port 的能力边界是什么(Repository=本地数据,Port=代码执行/事件/指标等外部能力)。

infrastructure/ 基础设施层

基础设施层负责把领域接口落到具体技术实现。

infrastructure/
├── dao/                          # SQLAlchemy 查询封装
│   └── po/                       # ORM 持久化对象(tool_definition / tool_invocation_log)
├── adapter/
│   ├── repository/               # 领域 repository 的实现
│   └── port/                     # 领域 port 的实现(含 ToolExecutorPort)
├── cache/                        # 进程内 TTL 缓存
└── logging/                      # 审计日志后台批量写入

这一层解决的问题:

  • 数据库表和领域对象之间的转换(tags/parameters/status 的 JSON↔对象)。

  • 工具定义、调用日志的持久化。

  • 工具定义的 cache-aside 缓存。

  • 代码执行器ToolExecutorPort):把工具的 codecompile+exec 到受控命名空间取出入口函数,按 name+sha256(code) 缓存编译产物,工具更新即 invalidate,按 is_async 分派。

  • 领域事件的进程内分发。

  • 审计日志后台批量写入。

  • 配置文件和数据库连接。

领域服务同时负责这些简单编排:

  • 工具创建/更新时构造聚合、校验、编译验证(编译失败回滚),并失效执行器缓存。

  • 工具执行前检查工具是否存在、是否启用、是否需要 ctx 配置。

  • 工具执行支持普通 def handler(...)async def handler(...)

  • 将执行成功或失败转为领域事件。

  • 草稿试跑(用编辑中、尚未保存的临时定义即时编译执行)与已存工具试跑(绕过 ENABLED 校验),两者记 source='test' 日志、不发 MCP 调用事件。

  • 分页查询日志、查询详情、统计和清理历史日志。

  • 将领域事件转为审计日志入队。

trigger/ 触发层

触发层是入站适配器,负责把外部协议转换为用例调用。

trigger/
├── http/                         # FastAPI 管理 API
└── mcp/                          # 官方 MCP low-level server 和 Streamable HTTP

HTTP 侧主要提供:

GET    /api/status
GET    /api/tools
GET    /api/tools/{name}
POST   /api/tools                      # 创建:code + parameters + is_async + tags + config
PUT    /api/tools/{name}               # 更新:同创建请求体
DELETE /api/tools/{name}               # 删除
POST   /api/tools/{name}/enable
POST   /api/tools/{name}/disable
POST   /api/tools/test                 # 草稿试跑(body 含 definition,不读/写库)
POST   /api/tools/{name}/test          # 已存工具试跑(绕过 ENABLED)
GET    /api/logs
GET    /api/logs/stats
GET    /api/logs/{id}
DELETE /api/logs?before=ISO_DATETIME

MCP 侧主要提供:

  • tools/list:只暴露启用状态的工具。

  • tools/call:把 MCP 调用转换为工具执行用例。

  • Streamable HTTP ASGI app:挂载到 FastAPI 的 /mcp

app/ 启动层

启动层是组合根,负责把所有层连接起来。

app/
├── application.py                # 进程启动入口,按配置选择 HTTP 或 stdio
├── container.py                  # dependency-injector 容器和 ApplicationContext
├── persistence.py                # session/事务边界和仓储装配
└── config/                       # YAML 配置加载、数据库/FastAPI/MCP 组件配置

这一层解决的问题:

  • 加载 resources/application.yaml 和可选 resources/application-local.yaml

  • 创建数据库 engine、session factory、DAO、repository、port(含 ToolExecutorPort)和 service。

  • 工具全部来自 DB(tool_definition 表),由 Web 端创建,不做工具目录自动发现。

  • 将事件订阅者注册到事件总线。

  • 构建 HTTP routers、MCP server 和前端静态挂载。

  • 在 lifespan 中建表、启动/停止日志写入器、管理 MCP session manager、释放数据库连接。

关键运行链路

管理 API 链路

HTTP request
  -> trigger/http controller
  -> domain service / repository interface
  -> infrastructure repository / dao / cache
  -> Response{code, info, data}

典型场景:

  • 工具列表:返回工具定义(含代码、参数、状态、input_schema、运行时配置)。

  • 创建/更新:在 service 中开启事务,校验并编译验证代码(编译失败回滚,返回 COMPILE_ERROR),写入后失效执行器缓存。

  • 启用/禁用:写入状态并失效缓存。

  • 配置随工具创建/更新写入,工具执行时若声明 ctx 可读取。

  • 日志查询:读取调用日志表并转换为前端 DTO。

MCP 工具调用链路

MCP tools/call
  -> trigger/mcp
  -> 工具执行服务
  -> 工具定义仓储查询(经缓存)
  -> domain 检查工具状态
  -> ToolExecutorPort 取/编译可调用对象(按 name+code 哈希缓存)
  -> 按 is_async 分派执行(同步服务中封装 async handler)
  -> 发布成功或失败领域事件
  -> 审计日志订阅者入队
  -> 文本结果返回 MCP client

这条链路保证:

  • 未注册工具返回统一错误码(UNKNOWN_TOOL)。

  • 禁用/草稿工具不会进入实际函数执行(TOOL_DISABLED)。

  • HTTP/MCP 入口通过线程池调用同步用例,避免同步数据库操作直接阻塞事件循环。

  • async 工具仍可执行,但会在线程内独立运行事件循环。

  • 日志和指标由事件订阅者处理,不阻塞主调用流程。

数据模型

默认 MySQL schema 见 init.sql

tool_definition
  动态工具定义:code、is_async、tags_json、parameters_json、config_json、status

tool_invocation_log
  工具调用日志、状态、耗时、错误、source(mcp/test)和创建时间

开发和测试可使用 sqlite:///path/to/dev.db。生产默认配置使用 mysql+pymysql

配置

默认配置在 resources/application.yaml

server:
  host: 127.0.0.1
  port: 8003
mcp:
  transport: streamable-http
  http-path: /mcp
  frontend-dist: frontend/dist
database:
  url: mysql+pymysql://root:pwd@127.0.0.1:3306/mcp_admin?charset=utf8mb4
  pool-size: 20
  max-overflow: 10

本地敏感配置写入 resources/application-local.yaml,会覆盖默认配置,且已被 .gitignore 忽略。

运行说明

本地敏感配置可写入 resources/application-local.yaml。例如开发时可使用 SQLite:

database:
  url: sqlite:///./dev.db

直接用模块方式启动服务,host、port、MCP transport 和路径都从 resources/application.yaml 读取:

python -m dd_arch

开发前端时也可以单独启动 Vite:

cd frontend
npm run dev

Vite 会把 /api/mcp 代理到 http://127.0.0.1:8003

在 Web 端创建工具

  1. 打开前端,进入 Tools 页,点击「New Tool」。

  2. 填写:

    • 名称(合法 Python 标识符)、描述。

    • 代码框:定义入口函数 def handler(...)async def handler(...)(异步工具需勾选「Async」开关并与代码一致)。需要读取运行时配置时,在签名里加 ctx 参数(ctx.config 为该工具的 KV 配置)。

    • 参数列表:逐行配置参数名/类型/必填/默认/描述,保存时由后端生成 JSON Schema。

    • tags(逗号分隔)。

  3. 保存前可在「Test Run」面板填入测试参数一键试跑(草稿试跑,无需先保存,编译失败会直接显示 COMPILE_ERROR)。

  4. 点击「Save Tool」:后端编译校验通过后入库(默认 DRAFT),编译失败返回 info 并不落库。

  5. 回到列表 enable 工具;启用后 MCP 客户端 tools/list 即可见、tools/call 可调用。

示例代码(同步):

def handler(a, b):
    return a + b

示例代码(异步):

import asyncio


async def handler(seconds):
    await asyncio.sleep(seconds)
    return f"slept {seconds}s"

当前数据库主链路是同步的;HTTP/MCP 入口会把用例调用放入线程池执行。

测试

运行后端测试:

uv run pytest

运行前端类型检查和生产构建:

cd frontend
npm run build

当前测试覆盖:

  • domain 不依赖 infrastructure、trigger、app、FastAPI、SQLAlchemy、MCP。

  • executor:sync/async 工具编译与分派、语法错误与 is_async 不符返回 COMPILE_ERRORbuild_input_schemacoerce_arguments(字符串转型与必填校验)。

  • 工具 CRUD:创建→编译校验→enable→MCP 列表可见;坏代码 create 返回 COMPILE_ERROR 且不落库。

  • 测试运行:草稿试跑返回结果且不创建工具、坏代码草稿试跑返回 ok=false, code=COMPILE_ERROR;已存工具试跑对 DRAFT/DISABLED 工具仍能执行;测试调用记 source='test' 且不计入 stats()

  • 执行用例:DISABLED 拦截并发 ToolFailedEvent;成功调用返回结果并经订阅者批量落库。

  • 50 个 async def handler + await asyncio.sleep(1) 通过线程池并发调用总耗时小于 5 秒。

已验证内容

当前已完成的本地验证包括:

  • src/dd_arch 导入检查(全包可 import,无残留旧模块引用)。

  • uv run pytest(18 项全绿)。

  • frontend/npm run build(vue-tsc 类型检查 + vite 构建通过)。

  • SQLite 库下 FastAPI lifespan 启动和关闭、MCP session manager 初始化。

  • /api/status/api/tools CRUD、/api/tools/test 草稿试跑和 enable(经 ASGI 实测)。

  • 工具定义 cache-aside、执行器编译缓存与失效。

  • domain 依赖边界扫描。

  • 前端统一信封拆包、工具编辑器与测试运行面板构建通过。

F
license - not found
-
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.

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/ollo-kitty/mcp'

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