自动化部署平台

Deploy MCP是一个为AI代理设计的自动化部署平台,提供一键式应用部署、数据库配置和域名管理,简化从代码到生产的全过程。

  • by stackconsult
  • Go
  • 本地部署
  • 更新于
在 GitHub 查看 stackconsult/mcp-for-mcp-s

使用说明

部署MCP

一个MCP来部署它们。 代理时代的基础设施。


动机

AI代理现在可以编写完整的应用程序。Claude Code、Cursor和Windsurf在几分钟内生成生产就绪代码。但是当部署的时候,代理遇到了一堵墙:

碎片化问题:

  • 需要铁路MCP进行托管
  • 数据库需要Neon MCP
  • 需要单独的工具来处理机密、DNS和SSL
  • 连接串的手动接线
  • 人类必须在每个平台上创建帐户

结果: 代理在几秒钟内构建,但部署需要数小时的人工干预。

# Today
Agent: "I've created your SaaS app. Here's the code."
Human: *creates Railway account*
Human: *creates Neon account*
Human: *deploys app manually*
Human: *provisions database*
Human: *copies connection string*
Human: *sets environment variables*
Human: *configures domain*
Human: *waits for SSL*
→ 2 hours later: "It's live"

# With Deploy MCP
Agent: create_service(repo="my-saas", name="my-saas")
→ 60 seconds later: "https://my-saas.ml.ink is live"

视觉

“代理人的互联网” --代理可以自主提供的基础设施。

部署MCP是 平台,而不仅仅是一个工具:

  • 用户通过身份验证 我们
  • 我们使用以下方式提供基础设施 我们的 提供者凭据
  • 代理通过以下方式部署 一个命令
  • 用户从不触摸提供商仪表板

核心原则

原理 说明
回购作为身份 github.com/user/app 是自然项目的关键
一笔交易 App+数据库+机密+域名在一次通话中
自动部署默认值 推送到GitHub→ 自动部署
平台抽象 用户永远看不到底层提供者
适合工作的工具 前端→ 边缘,后端→ 集装箱

认证

Deploy-MCP支持两个git提供者,每个提供者都有自己的身份验证模型:

Git提供者

提供者 身份 回购访问 Webhooks
GitHub (host=github.com) GitHub OAuth GitHub应用程序(安装令牌) GitHub应用webhook
内部git (host=ml.ink,默认) Firebase Auth 每个回购HTTPS令牌(mlg_...)通过git服务器 git服务器推送挂钩

GitHub流程: 用户通过GitHub OAuth登录,安装GitHub应用程序以访问仓库,然后使用 host=github.com.

内部git流(默认): 用户通过Firebase登录,获得一个自动配置的内部git帐户(petname用户名类似 awake-dassie).Repos是通过带有per-repo HTTPS令牌的git-server API创建的(mlg_...).不需要GitHub帐户。

MCP身份验证

代理向MCP服务器进行身份验证(https://mcp.ml.ink/mcp)通过API密钥:

Authorization: Bearer dk_live_abc123...

API密钥使用bcrypt(仅存储用于查找的前缀)进行哈希,并在每个请求上进行验证。

获取API密钥有两种方法:

1.手册 --从仪表板生成(/settings)

2.MCP OAuth --通过OAuth 2.0授权码+PKCS自动执行(适用于Claude Desktop等MCP客户端):

MCP Client                    Product Server              Frontend            User
    │                              │                          │                 │
    │─── GET /.well-known/ ───────▶│                          │                 │
    │◀── auth server metadata ─────│                          │                 │
    │                              │                          │                 │
    │─── POST /oauth/register ────▶│                          │                 │
    │◀── client_id ────────────────│                          │                 │
    │                              │                          │                 │
    │─── GET /oauth/authorize ────▶│                          │                 │
    │    (client_id, redirect_uri, │                          │                 │
    │     code_challenge, state)   │── store in cookie ──────▶│                 │
    │                              │── redirect ─────────────▶│── Firebase ────▶│
    │                              │                          │◀── login ───────│
    │                              │                          │                 │
    │                              │◀── POST /oauth/complete ─│  (consent)      │
    │                              │── generate API key ──────│                 │
    │                              │── generate auth code ────│                 │
    │                              │── return redirect_url ──▶│                 │
    │◀── redirect with code ───────────────────────────────────                 │
    │                              │                                            │
    │─── POST /oauth/token ───────▶│                                            │
    │    (code, code_verifier)     │── verify PKCE                              │
    │◀── access_token (API key) ───│                                            │

OAuth流在后台创建一个API密钥- access_token 返回的是一个真正的API密钥(dk_live_...),因此这两种身份验证方法使用相同的底层机制。

Webhooks(自动重新部署)

来源 事件 验证
GitHub应用程序 push, installation.created/deleted HMAC-SHA256(webhook密钥)
git服务器 push (接收后) 直接时间触发(无webhook)

两者都会触发相同的时态重新部署工作流,并使用确定性工作流ID进行重复数据删除。


技术栈

  • 语言:去吧
  • MCP框架: mcp走
  • 数据库:Postgres(带sqlc)
  • 认证:Firebase Auth+GitHub OAuth+GitHub应用程序
  • 编排:临时(部署工作流)
  • 容器编排:k3s
  • 构建:BuildKit+Railpack
  • 域名系统:PowerDNS(权威、自定义域)
  • 传输层安全:证书管理器(通过RFC2136的DNS-01)→ PowerDNS)
  • 入口:Traefik+Cloudflare LB+Hetzner LB(自定义域名)
  • Git:自定义git服务器(内部, git.ml.ink)
  • IAC公司:可靠
  • 计算:Hetzner(专用+云)
  • 数据库配置语言(SQLite)

MCP工具

工具 说明 要求
whoami 获取当前用户信息和GitHub应用程序状态 API密钥
create_service 从git仓库部署服务(host=ml.ink 或 github.com) API密钥
list_services 列出所有部署的服务 API密钥
get_service 获取服务详细信息,包括生成/运行时日志 API密钥
redeploy_service 重新部署服务以获取最新代码 API密钥
delete_service 删除服务及其k8资源 API键
create_resource 提供数据库(SQLite通过Turso) API键
list_resources 列出所有提供的资源 API密钥
get_resource 获取资源连接详细信息(URL+身份验证令牌) API密钥
delete_resource 删除资源 API密钥
create_repo 创建git仓库(host=ml.ink 违约,或 github.com) API密钥
get_git_token 获取临时git令牌以推送代码 API密钥
add_custom_domain 将自定义域附加到服务(需要委派区域) API密钥
remove_custom_domain 从服务 API密钥中删除自定义域
list_delegations 列出所有委派区域及其状态 API密钥

将MCP服务器添加到Claude代码中

# Production
claude mcp add --transport http mcpdeploy https://mcp.ml.ink/mcp --header "Authorization: Bearer <your-api-key>"

# Local development
claude mcp add --transport http mcpdeploy http://localhost:8081/mcp --header "Authorization: Bearer <your-api-key>"

Webhook入口仍然打开 https://api.ml.ink (deployer-server /healthz +git webhook接收器)。

应用程序二进制文件

有4个二进制文件分为两个运行时:

  • 铁路: cmd/server + cmd/worker
  • k3s集群: cmd/deployer-server + cmd/deployer-worker
二进制 路径 运行时 目的 任务队列 K8s清单
server cmd/server/main.go 铁路 产品API-GraphQL、MCP服务器、OAuth、Firebase授权 - -
worker cmd/worker/main.go 铁路 产品临时工--帐户工作流 default —
deployer-server cmd/deployer-server/main.go k3s(dp-system) Webhook接收器(GitHub),启动临时工作流 -- infra/eu-central-1/k8s/workloads/deployer-server.yml
deployer-worker cmd/deployer-worker/main.go k3s(dp-system) K8s部署工作器--构建、部署、删除、状态 deployer-eu-central-1 infra/eu-central-1/k8s/workloads/deployer-worker.yml

部署工作程序还运行DNS工作程序 tq-powerdns 任务队列(如果集群具有 has_dns=true).此worker通过PowerDNS处理区域创建、委派验证、通配符证书颁发和子域管理。

映射注释:概念性 k8s-server = deployer-server;概念 k8s-worker = deployer-worker.

部署服务器是 不 产品服务器。它只处理webhooks和 /healthz --没有GraphQL,没有MCP,没有OAuth。


部署工作流

当代理人来电时 create_service,会发生以下情况:

┌─────────────────────────────────────────────────────────────────────────────┐
│                           DEPLOYMENT FLOW                                    │
└─────────────────────────────────────────────────────────────────────────────┘

1. AGENT CALLS create_service
   ┌──────────┐     ┌──────────┐     ┌──────────┐
   │  Agent   │────▶│   MCP    │────▶│ Temporal │
   │  (via    │     │  Server  │     │ Workflow │
   │  Claude) │     │          │     │  Start   │
   └──────────┘     └──────────┘     └──────────┘

2. TEMPORAL WORKER (on ctrl-1) PICKS UP TASK
   ┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
   │ Create   │────▶│ Clone    │────▶│ Resolve  │────▶│ Build    │
   │ App in   │     │ Repo     │     │ Build    │     │ via      │
   │ Postgres │     │ (HTTPS)  │     │ Pack     │     │ BuildKit │
   └──────────┘     └──────────┘     └──────────┘     └──────────┘
                         │                                  │
                         ▼                                  ▼
                   Mints token (GitHub App       Push image to
                   or internal git token)        internal registry

3. DEPLOY TO K8S
   ┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
   │ kubectl  │────▶│ Create   │────▶│ Wait for │────▶│ Return   │
   │ apply    │     │ NS, Dep, │     │ Rollout  │     │ URL +    │
   │          │     │ Svc, Ing │     │ Ready    │     │ Status   │
   └──────────┘     └──────────┘     └──────────┘     └──────────┘

4. AUTO-REDEPLOY (Push to GitHub or internal git)
   ┌──────────┐     ┌──────────────┐     ┌──────────┐     ┌──────────┐
   │ Git Push │────▶│ GitHub:      │────▶│ Webhook  │────▶│ Temporal │
   │          │     │  webhook     │     │ Handler  │     │ Redeploy │
   └──────────┘     │ git-server:  │     │          │     │ Workflow │
                    │  post-receive│     └──────────┘     └──────────┘
                    └──────────────┘     Deterministic workflow ID
                                        from commit SHA (dedup)

工作流标识

GitHub webhook交付至少一次,因此同一推送事件可能会被多次交付。内部git通过后接收钩子直接触发部署。部署服务通过以下方式处理重复项:

  1. 从提交SHA导出确定性工作流ID
  2. 使用时态 REJECT_DUPLICATE 政策
  3. 如果该提交的工作流已在运行,则记录并返回成功

建筑

部署MCP使用一个4池k3s集群,将控制、操作、构建和运行问题分开。

核心理念

  1. 抽象

    • 代理与 项目 和 应用 (意图),而不是“服务器”或“容器”(实现)
    • MCP表面稳定;提供者是可替换的
  2. 安全

    • 用户代码运行时有强大的防护栏
    • 丢弃所有功能,无权限升级,无SA令牌
    • NetworkPolicy阻止专用网络、k8 API和元数据终结点
  3. 工作流程编排

    • 部署作为临时工作流运行,以提高可靠性
    • 内置自动重试、幂等性和可观察性

构建包

部署MCP支持多种构建策略:

构建包 用例
railpack (默认) 自动检测语言,生成BuildKit计划
dockerfile 通过BuildKit自定义Dockerfile
static nginx提供的静态文件

部署什么

服务类型

  • Web应用程序 --Next.js、Remix、SvelteKit等。(SSR或静态)
  • 应用程序编程接口 --Express、FastAPI、Go服务器
  • 后端 --WebSocket服务器、workers、cron作业

数据库资源

  • SQLite --通过Turso(管理、复制SQLite)

物理架构(4-pool k3s)

我们在专用节点池中分离控制、操作、构建和运行,因此CPU密集型构建永远不会滞后于实时应用程序。

拓扑学

┌──────────────────────────────────────────────────────────────┐
│  ctrl-1 (ctrl) — k3s Server (Hetzner Cloud VPS)               │
│                                                              │
│  k3s server process (etcd, API server, scheduler)            │
│  Temporal Worker                                             │
│  cert-manager, CoreDNS, metrics-server                       │
│                                                              │
│  Labels: pool=ctrl                                           │
└──────────────────────────────────────────────────────────────┘
         │ private network (Hetzner vSwitch, 10.0.0.0/16)
         │
┌────────┴─────────────────────────────────────────────────────┐
│  ops-1 (ops) — Observability & Git (Hetzner Dedicated)       │
│                                                              │
│  Loki, Prometheus, Grafana                                   │
│  git-server (custom, git.ml.ink)                             │
│                                                              │
│  Labels: pool=ops    Taint: pool=ops:NoSchedule              │
└──────────────────────────────────────────────────────────────┘
         │
┌────────┴─────────────────────────────────────────────────────┐
│  build-1 (build) — Builder + Registry (Hetzner Dedicated)    │
│                                                              │
│  BuildKit daemon (local cache + registry cache)              │
│  Docker Registry v2 (NVMe-backed)                            │
│                                                              │
│  Labels: pool=build    Taint: pool=build:NoSchedule          │
└──────────────────────────────────────────────────────────────┘
         │
┌────────┴─────────────────────────────────────────────────────┐
│  run-1+ (run) — Runners (Hetzner Dedicated)                  │
│                                                              │
│  Traefik (DaemonSet, hostNetwork)                            │
│  Customer containers (max-pods=800 per node)                 │
│                                                              │
│  Labels: pool=run                                            │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│  powerdns-1 — PowerDNS (Hetzner Cloud VPS)                     │
│                                                              │
│  Authoritative DNS for custom domains                        │
│  Local PostgreSQL backend                                    │
│  API on private network only (port 8081)                     │
│                                                              │
│  Standalone VM (not in k3s cluster)                           │
└──────────────────────────────────────────────────────────────┘

什么跑在哪里

ctrl(ctrl-1) --k3s服务器、临时工作器、证书管理器、CoreDNS

ops-1 --git服务器、洛基、普罗米修斯、Grafana

构建(build-1) --BuildKit守护进程,具有持久本地缓存+注册表缓存,Docker注册表

run(run-1+) --Traefik DaemonSet(ingress),客户容器(kubelet调优:最大Pod=800,并行镜像拉取)

powerdns-1 --PowerDNS权威DNS(独立VM,不是k3s节点)。管理自定义域的区域。

网络

  • 专用网络:Hetzner vSwitch(10.0.0.0/16)连接云和专用服务器
  • 公众进入真相来源:Cloudflare LB(*.ml.ink) → 运行节点源池→ Traefik→ k8s服务
  • TLS(*.ml.ink):通配符Let’s Encrypt证书通过证书管理器DNS-01,由Traefik TLSStore提供服务
  • **TLS(自定义域)**每个区域通配符证书(例如: *.apps.example.com)通过证书管理器DNS-01→ RFC2136→ PowerDNS

MCP接口(代理合同)

原则

  • 基于名称 --代理按名称而不是ID引用服务
  • 可发现的 — list_*, get_* 勘探工具
  • 日志是一流的 --代理可以通过以下方式进行自我调试 get_service

工具签名

服务

create_service(repo, host?, branch?, name, project?, build_pack?, port?, env_vars?, memory?, cpu?, install_command?, build_command?, start_command?)
list_services()
get_service(name, project?, include_env?, deploy_log_lines?, runtime_log_lines?)
redeploy_service(name, project?)
delete_service(name, project?)

资源(数据库)

create_resource(name, type?, size?, region?)
list_resources()
get_resource(name)
delete_resource(name)

Git

create_repo(name, host?, description?)
get_git_token(name, host?)

自定义域名

add_custom_domain(name, domain)
remove_custom_domain(name)
list_delegations()

区域委派本身(NS设置)是通过位于的web仪表板完成的 https://ml.ink/dns,而不是通过MCP工具。

身份

whoami()

数据库

当前实施情况

SQLite 通过 Turso --管理、复制SQLite数据库。

create_resource(name="my-db", type="sqlite", region="eu-central")

退货:

  • url --libSQL连接URL
  • auth_token --身份验证令牌(静态加密)

期货选择权

  • Postgres --通过霓虹灯或自托管
  • Redis/KV --通过Uptash进行缓存/队列
  • 带上你自己的 --连接字符串传递

集装箱登记处

内部Docker Registry v2在build-1(NVMe支持)上运行,仅可在专用网络上访问(10.0.1.5:5000).主机防火墙阻止端口5000与公共互联网连接。

每晚GC CronJob通过以下方式为每个服务保留最后2个标签 registry garbage-collect --delete-untagged.

图像被视为缓存/工件,而不是真相的来源——它们总是可以从源代码重建的。


自定义域名

用户可以通过委派子域区域来带来自己的域。然后,该平台控制该区域的DNS,颁发通配符证书,并立即创建子域。

运作原理

  1. 区域委派 (通过网站仪表板 ml.ink/dns):

    • 用户呼叫 delegate_zone("apps.example.com") → 获取TXT验证记录
    • 用户在以下位置添加TXT记录 _dp-verify.apps.example.com 证明所有权
    • 用户将NS记录委托给 ns1.ml.ink / ns2.ml.ink
    • 平台验证两者,然后激活该区域
  2. 激活 (临时工作流程 tq-powerdns 队列):

    • 在PowerDNS中使用SOA、NS和通配符A记录创建区域
    • 发出通配符证书 *.apps.example.com 通过证书管理器DNS-01(RFC2136→ PowerDNS)
    • 为Traefik创建证书加载器IngressRoute
  3. 子域附件 (通过MCP工具):

    • add_custom_domain(name="my-service", domain="api.apps.example.com")
    • 在PowerDNS中创建指向Hetzner LB的记录
    • 参照区域的通配符证书创建Traefik入口
    • 活在几秒钟内

流量(自定义域)

api.apps.example.com
  → Recursive resolver → NS ns1.ml.ink → PowerDNS (powerdns-1)
    → A 49.12.19.38 (Hetzner LB)
  → Hetzner LB → TCP passthrough → Traefik (run-1)
  → Traefik routes by Host header → customer pod

基础设施

  • PowerDNS 在powerdns-1(46.225.84.41/10.0.0.4)上运行——独立VM,而不是k3s节点
  • PostgreSQL后端(本地),仅限专用网络上的API(端口8081)
  • 证书管理器使用RFC2136和TSIG密钥在PowerDNS中创建DNS-01挑战记录
  • Hetzner LB执行TCP透传(无TLS终止),因此Traefik可以提供通配符证书
  • 未经验证的区域在7天后过期(反蹲)

操作手册

添加运行节点

通过Ansible配置新的运行节点:

# 1. Buy Hetzner Auction server, attach to Robot vSwitch
# 2. Add to inventory under run.hosts in infra/ansible/inventory/hosts.yml
# 3. Run the playbook:
ansible-playbook playbooks/add-run-node.yml --limit run-2
# 4. Add run-2 public IP to Cloudflare origin pool (Traffic → Load Balancing → run-nodes pool)

该剧本适用于:通用强化、vSwitch网络、k3s代理加入、注册表客户端配置和防火墙规则。

Cloudflare LB池管理

Cloudflare LB是公众进入的真相来源(*.ml.ink → 运行节点源池)。要添加或删除运行节点,请更新 run-nodes Cloudflare仪表板中的源池(流量→ 负载平衡→ 游泳池)。没有要管理的DNS记录。

群集管理

基础设施通过Ansible剧本进行管理 infra/ansible/:

  • site.yml --完整群集设置
  • add-run-node.yml --添加新的运行节点
  • upgrade-k3s.yml --滚动式k3s升级(也适用于kubelet/controller管理器配置更改)

K8s清单在 infra/eu-central-1/k8s/ 并由Ansible应用。


备份和还原

我们支持什么

  1. MCP状态(临界) -Suabase上的研究生(项目、资源、用户、API密钥)。Supabase处理备份。

  2. 用户数据(关键) --Turso数据库。提供程序本机备份。

  3. 内部Git仓库(重要) --托管于ops-1(/mnt/git-repos)NVMe RAID1用于内置冗余。

  4. 注册表映像(可重建) --被视为缓存/工件。始终可以从源代码重建。

恢复程序(灾难恢复)

场景:ctrl节点(ctrl-1)丢失

  1. 提供新的云服务器
  2. 跑 ansible-playbook site.yml --limit ctrl
  3. 从备份还原etcd或重新应用k8s清单
  4. 重新部署临时工

停机现实检查

  • 运行节点上的现有应用程序继续为流量提供服务——Traefik在每个运行节点上独立运行
  • 在恢复ctrl节点之前,您将失去部署/管理功能

安全配置

Pod安全

客户吊舱采用深度隔离防御运行:

层 保护
集装箱安全 放弃所有功能, allowPrivilegeEscalation: false, automountServiceAccountToken: false
网络入口 NetworkPolicy:默认拒绝,只允许相同的命名空间+Trafik
网络出口 网络策略:允许DNS+公共互联网,阻止 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,元数据(169.254.169.254)
注册表 主机防火墙:端口5000仅来自 10.0.0.0/16
配额 infra协调的每个命名空间资源配额(ctrl上的Ansible/systemd):默认40个CPU限制、40个CPU请求、40Gi内存限制/请求、200个Pod

主机级强化

看 infra/ansible/roles/firewall/ 用于iptables的规则阻止元数据端点并限制注册表对专用网络的访问。


非目标(为了理智)

  • 替换GitHub(我们将其与我们的内部git集成在一起)
  • 为用户构建完整的PaaS UI(MCP是接口)
  • 在第一天完美解决任意沙盒问题(发货基线+迭代)

设计说明

4池建筑

ctrl、ops、build和run池的分离确保了CPU密集型构建永远不会与客户工作负载或基础设施服务竞争。

临时工作流

所有部署都作为Temporal工作流运行,提供:

  • 临时故障时自动重试
  • webhook触发部署的标识性(来自提交SHA的确定性工作流ID)
  • 部署进度的可见性
  • 编排与业务逻辑的清晰分离
  • 工作流历史中没有秘密——在k8s secrets的活动中铸造的令牌

参考文献