1. 云基础设施工具服务器的定位
云基础设施是「一按就可能烧钱、一删就可能宕机」的领域。把云能力交给模型,收益巨大(自动化运维、快速排障、成本分析),风险也巨大(误删资源、越权访问、账单爆炸)。设计的核心是把「只读诊断」与「变更执行」彻底分开。
1.1 能力分类
# 观察类(安全): 查资源、看指标、读日志、列成本
# 计划类(安全): 生成变更计划(plan/diff),不改任何东西
# 变更类(危险): 创建/修改/删除资源(apply/scale/restart)
# 破坏类(极危): 删库、删集群、改 IAM、改网络
# 工具设计的第一原则: 让模型在"观察+计划"层自由,在"变更"层受限
1.2 三类基础设施工具
| 领域 | 代表 | 特点 | 风险点 |
|---|---|---|---|
| IaC | Terraform/OpenTofu | 声明式、有 plan | apply 改真实资源 |
| 编排 | Kubernetes | 命令式 + 声明式 | 删 ns、改 RBAC |
| 云 API | AWS/GCP/Azure | 面广、粒度细 | IAM、计费、数据 |
一句话:云工具的黄金法则是「先看后改、先 plan 后 apply」——把不可逆的 apply 与可逆的 plan 物理隔开。
2. 只读 vs 变更操作
工具清单的第一刀,就是按「是否改变状态」切分。只读工具默认开放,变更工具默认关闭或需审批。
2.1 只读工具
| 工具 | 作用 | 说明 |
|---|---|---|
| list_resources | 列资源 | 按标签/类型过滤 |
| describe_resource | 详情 | 配置、状态、关系 |
| get_metrics | 指标 | 时间范围、聚合 |
| get_logs | 日志 | 脱敏后返回 |
| estimate_cost | 成本估算 | 当前与预测 |
| get_iam_policy | 权限查询 | 用于自查 |
2.2 变更工具与分级
# L1 低危变更: 打标签、改描述、加注解(可逆、无影响)
# L2 中危变更: 扩缩容、重启、更新镜像(有影响但可控)
# L3 高危变更: 改网络/安全组、改 IAM、删无状态资源
# L4 极危变更: 删数据、删集群、改计费、动生产数据库
# 分级决定: 谁可用、是否需审批、是否需 dry-run、是否限流
2.3 统一的风险标注
// 每个工具声明风险级,网关据此套用策略
const TOOL_RISK: Record<string, RiskLevel> = {
list_resources: "none",
estimate_cost: "none",
scale_deployment: "medium",
update_security_group: "high",
delete_database: "critical",
};
function guard(tool: string, ctx: CallContext) {
const risk = TOOL_RISK[tool] ?? "high"; // 未标注一律按 high
if (risk === "critical" && !ctx.flags.allowCritical) {
throw new McpError(-32003, "critical operation requires approval");
}
if (risk === "high" && !ctx.approvalToken) {
throw new McpError(-32003, "high-risk operation requires dry-run + approval");
}
}
3. IaC 工具与 plan/apply 分离
Terraform 类工具天生适合 MCP:它的 plan/apply 工作流天然就是「先看后改」的模型。关键是把 plan 和 apply 做成两个工具,且 apply 必须消费一个具体的 plan。
3.1 plan 与 apply 的职责
# terraform_plan:
# 输入: 工作目录/变量/目标
# 输出: 计划摘要(+N ~M -K)、完整 plan 文件 ID、风险摘要
# 副作用: 无(只读 state 与 provider API)
# terraform_apply:
# 输入: plan 文件 ID(必须是本次生成的、未过期的)
# 输出: 执行结果
# 副作用: 改变真实资源
# 关键: apply 只接受 plan_id,不接受"重新生成计划"
3.2 plan 工具实现
async def terraform_plan(workspace: str, var_file: str | None = None,
targets: list[str] | None = None) -> dict:
ws = resolve_workspace(workspace) # 白名单校验
plan = await tf.plan(ws, var_file=var_file, targets=targets, lock_timeout="30s")
# 解析计划,做风险摘要(尤其是销毁操作)
summary = {
"add": plan.count("add"),
"change": plan.count("change"),
"destroy": plan.count("destroy"),
"destroys": plan.resource_addresses("destroy"), # 将被销毁的资源清单
}
# 计划文件短期有效,绑定会话
plan_id = plan_store.put(ws, plan.file, ttl_seconds=900,
bind_to=current_session())
return {
"plan_id": plan_id,
"summary": summary,
"risk": classify_plan_risk(summary), # none/low/high
"expires_in": 900,
}
3.3 apply 工具实现
async function terraformApply(planId: string, ctx: CallContext) {
const entry = planStore.get(planId);
if (!entry) throw new McpError(-32602, "unknown or expired plan_id");
if (entry.bind_to !== ctx.sessionId) {
throw new McpError(-32003, "plan belongs to another session"); // 防跨会话盗用
}
const risk = classifyPlanRisk(entry.summary);
if (risk === "high" && !(await approvals.isGranted(ctx, planId))) {
throw new McpError(-32003, "high-risk plan requires approval");
}
audit.record({ op: "tf_apply", plan_id: planId, summary: entry.summary });
return tf.apply(entry.workspace, entry.file);
}
一句话:apply 必须消费「一个具体的、绑定到本会话的、未过期的 plan」——这三点是防止「模型临时改主意乱 apply」的关键。
4. Kubernetes 工具设计
K8s 工具是双刃剑:kubectl get 无害,kubectl delete ns 能删掉整个环境。
4.1 K8s 工具分级
| 工具 | 风险 | 说明 |
|---|---|---|
| get_pods / describe | 无 | 只读诊断 |
| get_logs | 无 | 日志(脱敏) |
| get_events | 无 | 事件 |
| scale_deployment | 中 | 扩缩容 |
| rollout_restart | 中 | 重启 |
| set_image | 中 | 更新镜像 |
| apply_manifest | 高 | 应用任意清单 |
| delete_resource | 高 | 删除资源 |
| delete_namespace | 极危 | 删命名空间 |
4.2 命名空间与集群隔离
ALLOWED_NAMESPACES = {"dev", "staging", "ai-sandbox"}
DENIED_NAMESPACES = {"kube-system", "prod", "monitoring"}
def assert_namespace(ns: str) -> None:
if ns in DENIED_NAMESPACES:
raise McpError(-32003, f"namespace {ns} is protected")
if ns not in ALLOWED_NAMESPACES:
raise McpError(-32003, f"namespace {ns} is out of scope")
def assert_scope(manifest: dict) -> None:
ns = manifest.get("metadata", {}).get("namespace", "default")
assert_namespace(ns)
# 禁止跨集群资源(ClusterRole 等)
if manifest.get("kind") in {"ClusterRole", "ClusterRoleBinding", "CustomResourceDefinition"}:
raise McpError(-32003, "cluster-scoped resources are not allowed")
4.3 服务端 dry-run
# K8s 原生 dry-run: 校验但不落地
kubectl apply -f manifest.yaml --dry-run=server -o yaml
# 先 dry-run 校验,再真实 apply —— 让模型"先看会发生什么"
kubectl apply -f manifest.yaml --dry-run=server \
&& kubectl apply -f manifest.yaml
5. 云 API 工具(AWS 类)
云 API 面极广,不可能也不该把全部能力暴露给模型。原则是「按需封装、最小能力」。
5.1 封装策略
# 1) 不暴露通用"执行任意 API 调用"的工具(等于给万能钥匙)
# 2) 按场景封装: "查 EC2 实例"、"看 S3 桶大小"、"列 RDS"
# 3) 每个工具只映射到少数几个只读或低危 API
# 4) 参数做白名单: 只允许安全的过滤条件
# 反面教材: 一个 aws_call(service, action, params) 工具 = 无限制
5.2 只读云工具示例
# 只封装只读的 Describe/List 类 API
READONLY_ACTIONS = {
"ec2:DescribeInstances", "ec2:DescribeSecurityGroups",
"s3:ListBuckets", "s3:GetBucketLocation",
"rds:DescribeDBInstances", "cloudwatch:GetMetricData",
"ce:GetCostAndUsage", # 成本查询
}
async def cloud_query(service: str, action: str, params: dict) -> dict:
full = f"{service}:{action}"
if full not in READONLY_ACTIONS:
raise McpError(-32003, f"action {full} is not allowed")
return await aws_client.call(service, action, sanitize(params))
5.3 IAM 与凭证边界
| 手段 | 说明 |
|---|---|
| 专用角色 | MCP 服务器用独立 IAM 角色,非个人凭证 |
| 只读策略 | 默认只挂 ReadOnlyAccess |
| 资源限定 | 策略 Resource 限定到具体资源 ARN |
| 条件键 | 限制来源 IP、MFA、时间窗 |
| 会话标签 | 用 STS 会话标签标记「来自 MCP」 |
| 权限边界 | Permission Boundary 兜底 |
一句话:永远不要暴露一个「执行任意云 API」的工具——那等于把整个云账号的钥匙交给模型。
6. dry-run 与审批
变更类操作必须能「先看后做」。dry-run 是低成本的预演,审批是高成本的兜底。
6.1 dry-run 的层次
# 1) 平台原生 dry-run: K8s --dry-run=server, AWS DryRun=true
# 2) 计划式: Terraform plan(天然就是 dry-run)
# 3) 模拟式: 自建 diff 引擎,对比"变更前后"状态
# 4) 只读预检: 检查前置条件(配额、依赖、冲突)
# 不是所有操作都支持 dry-run,不支持的要有替代(如 diff 预演)
6.2 变更审批流
async function changeWithApproval(req: ChangeRequest, ctx: CallContext) {
// 1) 预检 + dry-run
const preview = await previewChange(req);
if (preview.errors.length) {
throw new McpError(-32602, `preflight failed: ${preview.errors.join("; ")}`);
}
// 2) 风险评估
const risk = assessRisk(req, preview);
if (risk.level === "critical") {
const ticket = await approvals.request({
principal: ctx.principal,
change: req,
preview: preview.summary,
blast_radius: preview.blastRadius,
});
return { status: "pending_approval", ticket_id: ticket.id, preview: preview.summary };
}
// 3) 执行 + 审计
audit.record({ op: "change", req, preview: preview.summary, risk: risk.level });
return executeChange(req);
}
6.3 爆炸半径评估
def assess_blast_radius(change: dict) -> dict:
r = {"level": "low", "affected": []}
if change["kind"] == "delete_namespace":
r = {"level": "critical", "affected": ["all pods/services in ns"]}
elif change["kind"] == "update_security_group":
r = {"level": "high", "affected": ["network reachability"]}
elif change["kind"] == "scale":
r = {"level": "medium",
"affected": [f"replicas {change['from']}→{change['to']}"]}
r["prod"] = change.get("env") == "prod"
return r
7. 凭证最小权限
云工具的安全基石是凭证。凭证越权,前面所有工具层的防护都会被绕过。
7.1 权限最小化原则
# 1) 一工具一角色: 不同工具用不同凭证(读工具只读、写工具限定资源)
# 2) 默认拒绝: 策略从"什么都不允许"开始加白名单
# 3) 资源限定: 不用 "*",写到具体 ARN 或前缀
# 4) 短期凭证: 用 STS 临时凭证,不用长期 AccessKey
# 5) 会话标签: 标记调用来源,便于审计与追责
# 6) 定期轮换: 凭证有效期 + 自动轮换
7.2 最小权限策略示例
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyForDiagnostics",
"Effect": "Allow",
"Action": ["ec2:Describe*", "cloudwatch:GetMetricData", "logs:FilterLogEvents"],
"Resource": "*"
},
{
"Sid": "ScaleOnlySandboxEKS",
"Effect": "Allow",
"Action": ["eks:UpdateNodegroupConfig"],
"Resource": "arn:aws:eks:ap-northeast-1:123456789012:nodegroup/sandbox/*"
}
]
}
7.3 凭证不进模型上下文
# 凭证的"可见性"边界
# 1) 凭证只在 MCP 服务器进程内使用,绝不作为工具返回值
# 2) 工具输出里若出现 token/密钥 → 脱敏
# 3) 日志里不打印凭证
# 4) 模型无法通过任何工具"读到"凭证(没有这种工具)
# 模型不需要凭证,只需要能力——这是设计的分界线
一句话:凭证最小权限是云工具的「最后一道墙」——工具层的所有校验都可以被绕过,凭证权限绕不过。
8. 成本与爆炸半径控制
云资源「一按就烧钱」,一次失控的扩缩容可能产生天价账单。成本控制必须是工具的一等公民。
8.1 成本护栏
| 护栏 | 机制 |
|---|---|
| 单次变更上限 | 扩容不超过 N 倍 / N 个实例 |
| 累计预算 | 本会话/本日的估算成本上限 |
| 变更前估算 | 每个变更工具先返回成本估算 |
| 异常熔断 | 成本增速异常则暂停变更 |
| 标签约束 | 只允许操作带指定标签的资源 |
8.2 变更前的成本估算
async function scaleDeployment(req: ScaleRequest, ctx: CallContext) {
const current = await k8s.getDeployment(req.ns, req.name);
const delta = req.replicas - current.spec.replicas;
const unitCost = await pricing.nodeHourlyCost(current.nodeSelector);
const hourlyDelta = delta * unitCost;
const monthlyDelta = hourlyDelta * 730;
// 超预算则拒绝,并给出具体数字
if (monthlyDelta > ctx.budget.maxMonthlyDelta) {
throw new McpError(
-32003,
`scale would add $${monthlyDelta.toFixed(2)}/mo, exceeding budget ` +
`$${ctx.budget.maxMonthlyDelta}`
);
}
return { status: "ok", preview: { from: current.spec.replicas, to: req.replicas, monthlyDelta } };
}
8.3 爆炸半径清单
# 每次变更前问四个问题
# 1) 影响范围: 一个实例、一个服务、一个环境、还是整个账号?
# 2) 可逆性: 能回滚吗? 回滚需要多久?
# 3) 依赖面: 谁会依赖被改的东西?(上游/下游)
# 4) 时间点: 现在是业务高峰吗? 有变更窗口吗?
# 答不上来 → 不允许自动执行
9. 与 GitOps 结合
最安全的云操作方式,是「不让模型直接改云,而是让模型改 Git,由 GitOps 落地」。
9.1 GitOps 模式
# 传统: 模型 → 云 API(直改,审计分散)
# GitOps: 模型 → Git PR(改声明)→ CI 校验 → 人审 → 合入 → 控制器落地
# 优势:
# 1) 所有变更经过 Git 评审与审计
# 2) 声明与状态一致,可回滚(revert 提交)
# 3) 模型能力限制在"改声明",不碰真实资源
# 模型是"变更作者",Git 是"变更通道",GitOps 控制器是"执行者"
9.2 混合策略
# 按操作类型选择通道
operations:
declarative: # 声明式变更走 GitOps
examples: [deployment, configmap, ingress]
channel: gitops_pr
diagnostic: # 诊断走只读工具
examples: [get_pods, get_metrics, get_logs]
channel: readonly_api
imperative: # 命令式变更走审批 API
examples: [rollout_restart, scale]
channel: approved_api
require: [dry_run, approval]
10. 常见陷阱
- 暴露通用云 API 工具:
aws_call(action, params)等于交出账号——按场景封装。 - apply 重新生成计划:模型 apply 时改了参数——apply 只消费 plan_id。
- plan 不绑会话:A 会话的 plan 被 B 会话 apply——绑定 + 过期。
- 只读工具用写凭证:读工具却挂了 AdministratorAccess——一工具一角色。
- 无 dry-run 直接变更:直接删资源无法预演——强制 dry-run 或 diff。
- 不估成本就扩容:一次误操作账单翻倍——变更前估算 + 预算上限。
- 命名空间不隔离:模型能删 kube-system——白名单 + 保护名单。
- 凭证回传模型:工具输出里带 token——脱敏,凭证永不进上下文。
11. 总结
MCP 云基础设施与 IaC 工具服务器,是收益与风险都极高的一类工具。安全落地的核心是四条铁律:读写分离(只读默认开放、变更分级受限、危险操作审批)、plan/apply 分离(apply 只消费绑定会话的、未过期的具体计划)、dry-run 前置(能预演就预演,不能预演就用 diff 替代)、凭证最小权限(一工具一角色、资源限定、短期凭证、永不回传模型)。在此之上叠加成本护栏(变更前估算、预算熔断)与爆炸半径评估(范围、可逆性、依赖面、时间点),并优先采用 GitOps 模式(让模型改声明而非改真实资源),就能把「模型操作云」从高危动作变成可控流程。这与 https://plumephp.com/mcp-security-practices/ 的安全原则、https://plumephp.com/mcp-production-deployment/ 的部署实践、以及 Terraform 的 IaC 方法论一脉相承。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。