部署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 "
# Local development
claude mcp add --transport http mcpdeploy http://localhost:8081/mcp --header "Authorization: Bearer "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通过后接收钩子直接触发部署。部署服务通过以下方式处理重复项:
- 从提交SHA导出确定性工作流ID
- 使用时态
REJECT_DUPLICATE政策 - 如果该提交的工作流已在运行,则记录并返回成功
______________________________________________________________________
建筑
部署MCP使用一个4池k3s集群,将控制、操作、构建和运行问题分开。
核心理念
- 抽象
- 代理与 项目 和 应用 (意图),而不是“服务器”或“容器”(实现) - MCP表面稳定;提供者是可替换的
- 安全
- 用户代码运行时有强大的防护栏 - 丢弃所有功能,无权限升级,无SA令牌 - NetworkPolicy阻止专用网络、k8 API和元数据终结点
- 工作流程编排
- 部署作为临时工作流运行,以提高可靠性 - 内置自动重试、幂等性和可观察性
______________________________________________________________________
构建包
部署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连接URLauth_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,颁发通配符证书,并立即创建子域。
运作原理
- 区域委派 (通过网站仪表板
ml.ink/dns):
- 用户呼叫 delegate_zone("apps.example.com") → 获取TXT验证记录 - 用户在以下位置添加TXT记录 _dp-verify.apps.example.com 证明所有权 - 用户将NS记录委托给 ns1.ml.ink / ns2.ml.ink - 平台验证两者,然后激活该区域
- 激活 (临时工作流程
tq-powerdns队列):
- 在PowerDNS中使用SOA、NS和通配符A记录创建区域 - 发出通配符证书 *.apps.example.com 通过证书管理器DNS-01(RFC2136→ PowerDNS) - 为Traefik创建证书加载器IngressRoute
- 子域附件 (通过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应用。
______________________________________________________________________
备份和还原
我们支持什么
- MCP状态(临界) -Suabase上的研究生(项目、资源、用户、API密钥)。Supabase处理备份。
- 用户数据(关键) --Turso数据库。提供程序本机备份。
- 内部Git仓库(重要) --托管于ops-1(
/mnt/git-repos)NVMe RAID1用于内置冗余。
- 注册表映像(可重建) --被视为缓存/工件。始终可以从源代码重建。
恢复程序(灾难恢复)
场景:ctrl节点(ctrl-1)丢失
- 提供新的云服务器
- 跑
ansible-playbook site.yml --limit ctrl - 从备份还原etcd或重新应用k8s清单
- 重新部署临时工
停机现实检查
- 运行节点上的现有应用程序继续为流量提供服务——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的活动中铸造的令牌
______________________________________________________________________
