跳到主要内容

为企业团队封装专属 CloudBase MCP

一句话定义:给团队发一份 MCP 配置,员工在自己的 AI IDE 里就能部署和管理 CloudBase 应用——不用腾讯云账号、看不到别人的数据、删不掉生产资源。

预计耗时:1 小时 | 难度:进阶

更简单的路:如果只是想先用起来,直接看「先看:多数团队不需要封装」——一句提示词接入,不用封装。本文其余内容是要管控之后的路线。

适用与不适用​

适用:内部研发平台统一入口、对外 SaaS 让客户生成应用、按人按项目隔离环境、需要审计调用记录。

不适用:个人开发者自用(直接配官方 MCP);只想让模型「通常不做」某事(写 System Prompt 就够);客户要求零明文凭证(走自定义授权码,本篇用 API Key);国际站(端点和账号体系不同)。

先看:三层解法,按需取用​

企业接入的「管控」不是只有封装一条路,是三个层面的解法,约束强度递进。定位先说清:第 1 层不改任何代码,配专家和 skill 就够;当专家 / skill 的软约束满足不了管控要求(拦不住误删越权、要凭证生命周期、要审计),需要做一个企业专属的 MCP——这就进入第 2 / 3 层,是涉及 AI 参与开发的工作(本文教的就是怎么做)。第 2 与第 3 层的差别只在部署位置:跑员工机器,还是跑企业服务器。

第 1 层 · 定制专家 / System Prompt / Skill:不改任何代码,通过提示词和 skill 给 AI 注入企业规范——创建云函数强制带 VPC 配置、统一资源命名,甚至把授权 Helper 打包进专家实现零明文授权。约束在提示词层(软,可能被绕过),成本最低。做法见企业级 Agent 平台集成最佳实践。

第 2 层 · 封装本地 MCP(本文):协议层硬管控——工具裁剪、危险动作拦截、凭证按人下发与审计。跑在员工机器(stdio)。

第 3 层 · 封装远端 MCP:wrapper 作为远端服务统一运营,员工零安装。代码与第 2 层同源,部署流程已按远端形态设计(源码走 COS 通道,不依赖本地文件),差异点见文末「远端形态的差异」。

接入本身不分层:第 1 层的门槛就是一句提示词——员工只拿环境 ID 和 API Key,在任意 AI 工具的对话里发:

帮我把 CloudBase 接好,按下面做:
1. 打开 https://docs.cloudbase.net/skill.md,按说明完成接入。
2. 接入完成后告诉我,并建议最相关的下一步。
我的环境 id 是 env-xxxxx,apikey 是 ak-xxxxx

AI 会自己打开官方接入说明(skill.md)完成配置,员工不需要懂 MCP 配置格式。

三层对照:

第 1 层 · 定制专家第 2 层 · 本地 MCP(本文)第 3 层 · 远端 MCP
约束在哪提示词层(软,可能被绕过)协议层(硬,绕不过)协议层 + 服务端统一运营
工具面官方全量,含 capi 透传按企业策略裁剪同左
危险动作提示词规范行为协议层拦截协议层拦截
凭证可打包 Helper 零明文授权平台按人下发、可吊销、有审计服务端持有,员工零凭证
部署位置员工配置里装专家 / skill员工机器(stdio)企业服务器(HTTP 端点)

不想自己评估选哪层? 把官方的 AI 集成提示词发给你的 AI 编程助手(Cursor、Copilot、企业自建 Agent 均可),它会读文档、向你澄清架构与合规要求、给出选型建议并协助落地:

我想在当前项目中集成腾讯云开发(CloudBase)的企业级 Agent 平台能力。

请先阅读以下参考文档:
1. CloudBase Skill 规范:https://docs.cloudbase.net/skill.md
2. 企业级平台集成最佳实践:https://docs.cloudbase.net/solutions/cloudbase-platform-edition/enterprise-integration

如你的环境已安装 CloudBase 的 **cloud-api-operations** skill,优先加载它,并阅读其 `references/recipes/enterprise-mcp-wrapper.md`(封装企业专属 MCP 的完整实操);未安装则按上述文档操作。

结合文档,请先向我了解我们目前的系统架构、鉴权方式及合规要求,与我澄清并评估最合适的集成方案(三层:定制专家 / System Prompt、封装本地 MCP、封装远端 MCP)。方案确认后,再一步步指导或协助我完成落地实施。实施中涉及云 API 的接口名、参数与行为,以文档与实测为准,不从记忆推断。

判断标准一句话:先能用,再软约束,最后硬管控——提示词层拦不住的(误删、越权、凭证外泄),才需要走到封装。

整体结构​

两个面:员工日常操作全在对话里(部署、预览、查状态、数据库);发布动作只在流水线配置里出现,由维护者配一次、git push 自动触发,员工不接触 CLI。

流水线用 CloudBase 自带的发布流水线(代码推上去自动构建部署),本文的「流水线」都指它。团队已有自有 CI(GitHub Actions、Jenkins 等)也能接入,配置方式本文不展开。

wrapper 同一套代码有两种部署形态:本地 stdio(上图,跑在员工机器)或远端 HTTP 服务(跑在企业服务器,员工配置里连 URL 而不是 npx)。第四步的部署流程对两种形态通用——这就是源码统一走 COS 通道的原因。

第一步:给每人一个环境,下发配置​

用 CreateEnv 按人(或按项目)开环境,维护 userId → envId 映射。两条隔离原则:一人一个环境(凭证出事只影响一个人)、一个应用一个环境(多应用混用会集合混杂、函数重名硬覆盖、静态托管互相冲掉,也没法按应用计量)。然后给每人下发这份配置:

{
"mcpServers": {
"cloudbase": {
"command": "npx",
"args": ["-y", "@your-org/cloudbase-wrapper"],
"env": {
"CLOUDBASE_ENV_ID": "env-xxxxx",
"CLOUDBASE_API_KEY": "ak-xxxxx"
}
}
}
}

配置里的 @your-org/cloudbase-wrapper 是占位符——它就是第二步做好的 wrapper 发布成的包名,换成你们自己的。没做过 npm 发布的话看第二步末尾「把 wrapper 发出去」。

Key 是环境级凭证(泄露只影响一个人的环境),用 CreateApiKey 创建,可配白名单权限(比如给前端团队只开「静态托管-写」)。三条纪律:一人一 Key、离职即删、定期轮换(单环境最多 5 个,滚着换不断服)。

交付给 AI 的凭证形态只有一种:环境 ID + API Key。API Key 是长期根凭证,MCP 内部换 30 分钟 STS 临时凭证出网、过期自动重换;STS 三件套是换证产物,不能单独当交付物——只交 STS 的话,过期后没有根凭证可重换,整条链路瘫痪。

wrapper 部署为远端服务时,这份配置换成 URL 连接("url": "https://mcp.your-org.com/..." 加鉴权头),凭证改由服务端持有、不下发到员工机器——下发内容和形态按企业安全基线选,后面的步骤不变。

第二步:写 wrapper​

一个文件搞定。官方 MCP 跑在同进程,in-memory 传输对接,外层做三件事:裁工具、拦危险动作、验凭证。

import { createRequire } from 'node:module';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';

// 必须走 CJS 入口:ESM 入口有未声明的内部依赖
const require = createRequire(import.meta.url);
const { createCloudBaseMcpServer } = require('@cloudbase/cloudbase-mcp');

// 插件组合是平台方策略,写死在 wrapper 里,员工不配置。按需增减:
const PLUGINS = 'env,database,functions,storage,hosting,gateway,cloudrun,apps'.split(',');
// 不传时官方默认全开,含能透传任意云 API 的 capi——企业场景务必排除它

const upstream = await createCloudBaseMcpServer({
name: 'cloudbase-mcp', cloudMode: false,
pluginsEnabled: PLUGINS,
cloudBaseOptions: { envId: process.env.CLOUDBASE_ENV_ID },
});

const [ct, st] = InMemoryTransport.createLinkedPair();
await upstream.connect(st);
const client = new Client({ name: 'wrapper', version: '1.0.0' }, { capabilities: {} });
await client.connect(ct);

// 危险动作:官方二次确认清单的删除类,按语义前缀兜底(delete/destroy/remove...)
const isDenied = (tool, action) =>
/^(delete|destroy|remove|clear|truncate|drop)/i.test(action ?? '');

const wrapper = new Server({ name: 'cloudbase-wrapper', version: '1.0.0' }, { capabilities: { tools: {} } });

wrapper.setRequestHandler(ListToolsRequestSchema, async () => {
const { tools } = await client.listTools();
const visible = [];
for (const tool of tools) {
// 被禁动作从参数 enum 里剪掉,模型在参数定义里就看不见
const p = tool.inputSchema?.properties?.action;
if (p?.enum) {
p.enum = p.enum.filter((a) => !isDenied(tool.name, a));
if (p.enum.length === 0) continue;
}
visible.push(tool);
}
return { tools: visible };
});

wrapper.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args = {} } = req.params;
if (isDenied(name, args.action)) {
return { content: [{ type: 'text', text: `${name}.${args.action} 被平台策略禁止,重试无效。` }], isError: true };
}
console.error('[audit]', JSON.stringify({ ts: Date.now(), env: process.env.CLOUDBASE_ENV_ID, call: `${name}.${args.action ?? ''}` }));
return client.callTool(req.params);
});

// 凭证自检:只有 single_env + env 来源才放行,防止静默回落到账号级全局登录态
const res = await client.callTool({ name: 'auth', arguments: { action: 'status' } });
const text = (res.content ?? []).map((c) => c.text ?? '').join('\n');
const scope = /credential_scope["'=:\s]+(\w+)/i.exec(text)?.[1];
if (scope && scope !== 'single_env') {
console.error(`[cbw] 凭证作用域异常(${scope}),API Key 未生效,拒绝服务`);
process.exit(1);
}

await wrapper.connect(new StdioServerTransport());
console.error('[cbw] ready');

三条不能省:

  1. 日志全走 console.error。 stdio 是按行分隔的 JSON-RPC,stdout 多一个字符客户端就崩
  2. pluginsEnabled 必须显式传。 不传默认全开,包括 capi——它能调任意云 API,等于绕过整个白名单
  3. 过滤和拦截都要。 只裁列表,模型会猜工具名硬调;只拦调用,模型看得见调不动会反复重试

把 wrapper 发出去​

第二步这份代码就是一个标准的 npm 包:配上 package.json(name 写成你们的包名,如 @your-org/cloudbase-wrapper),npm publish 发出去,第一步配置里的 npx -y 包名 就能自动下载执行——员工机器上不需要预先安装任何东西。

没有公共 npm 源的企业,两个替代:发到企业内部源(如制品库、verdaccio,员工配置多一行 .npmrc 指向内部源),或直接从内部 git 仓库安装(npx git+ssh://...,免发布)。发版由平台方统一做,建议版本号固定(如 包名@1.2.0),升级由平台推新版本,避免 npx 每次拉到不一样的版本。

第三步:验一次凭证​

API Key 有个隐蔽行为:解析条件是「Key 与 CLOUDBASE_ENV_ID 同时在环境变量」,且换证失败会被静默吞掉、回落到机器上可能存在的全局登录态——wrapper 会带着账号级权限继续跑而不自知。

所以第二步代码里的自检不能删:credential_scope=single_env 且 credential_source=env 才算 Key 生效。调试时可用环境变量临时跳过自检做协议层验证,生产默认开启。

好消息是 API Key 本身不是每次调用的凭证——MCP 先换 30 分钟临时密钥再出网,Key 只在换证时用一次。

第四步:部署​

部署是同一个动作,只有触发方式不同:开发时在对话里说「部署」,模型走 MCP 工具;正式发布时同一份配置进 git,由 CloudBase 自带的发布流水线执行。两个触发产出的是同一个 CloudApp 的连续版本。

本节代码均为调用 MCP 工具的伪代码示意(通过 CloudBase MCP 或企业 wrapper MCP),重点是参数与流程,不是可直接运行的 JS。

源码统一走 COS 通道:getUploadUrl 拿预签名地址 → PUT 源码 zip → deployApp 带 source: { type: 'zip', cosTimestamp } 触发构建(Type 枚举只有 git / zip,二阶段上传时 repo 留空,平台按时间戳定位刚上传的包)。三个例子同一模式。这套流程不依赖任何本地文件——调用方在哪里(本地 stdio 或远端 HTTP)都能执行,wrapper 被部署为远端 MCP 时同样成立。本地形态另有 filePath 快捷方式(MCP 直接读本机目录打包上传),不进示例。

按场景对号入座:

你的应用走哪条路径用到的工具
纯静态 / 标准框架一次上传 + 一次 deployAppmanageApps
http 函数(自定义镜像后端)上传 + 构建镜像 → 挂成函数manageApps + manageFunctions
前后端一体上传 + 一次 deployApp:serviceList + routesmanageApps

例 1:纯静态 / 标准框架​

// 1. 拿预签名上传地址
const { uploadUrl, uploadHeaders, unixTimestamp } = await manageApps({
action: 'getUploadUrl',
serviceName: 'crm-portal',
});

// 2. PUT 源码 zip 到 uploadUrl(带上 uploadHeaders)

// 3. 触发部署:云端拉包构建
await manageApps({
action: 'deployApp',
serviceName: 'crm-portal', // 域名:crm-portal-<envId>.webapps.tcloudbase.com
source: { type: 'zip', cosTimestamp: unixTimestamp }, // 即第 1 步返回的 unixTimestamp
framework: 'react',
installCmd: 'npm ci',
buildCmd: 'npm run build',
buildPath: 'dist', // 相对项目根的产物目录
});

// 4. 轮询状态
await queryApps({ action: 'getAppVersion', serviceName: 'crm-portal', buildId });

// 5. 失败看日志
await queryApps({ action: 'getBuildLog', serviceName: 'crm-portal', buildId });

参数速查:buildPath 是相对项目根的产物目录;非标准构建不用换工具,installCmd / buildCmd 可换成任意命令(如 pnpm --filter app build)。

例 2:http 函数(自定义镜像后端)​

构建和部署是两段:构建容器里没有部署命令——构建的终点是把镜像推上仓库,部署发生在容器外的第二次工具调用。

// 0. 上传源码(同例 1:getUploadUrl → PUT zip;custom 场景 getUploadUrl 也带 deployType: 'custom')

// Stage A:构建镜像并推送(manageApps custom,容器内)
await manageApps({
action: 'deployApp',
serviceName: 'order-api',
deployType: 'custom',
source: { type: 'zip', cosTimestamp: unixTimestamp }, // 同例 1:getUploadUrl 返回的 unixTimestamp
// IMAGE = TCR 实例地址/命名空间/服务名,企业自定;tag 必须用 $CLOUDBASE_VERSION_NAME
customSteps: [
{ name: 'build', command: 'docker build -t $IMAGE:$CLOUDBASE_VERSION_NAME .' },
{ name: 'push', command: 'docker push $IMAGE:$CLOUDBASE_VERSION_NAME' },
],
});
// 登录凭证不用传:平台内置注入 $API_SECRET_ID / $API_SECRET_KEY / $API_TOKEN,
// 签 TCR CreateInstanceToken 换临时 token 后 docker login --password-stdin

// Stage B:把镜像挂成 http 函数(容器外)
await manageFunctions({
action: 'createFunction',
func: {
name: 'order-api',
runtime: 'CustomImage',
imageConfig: { imageUri: '$IMAGE:$CLOUDBASE_VERSION_NAME' },
},
});
// 后续迭代用 updateFunctionCode + imageConfig,不用重建函数

要点:容器监听 9000 端口并自己起 HTTP 服务;镜像 ≤ 500MB(冷启动);imageUri 必须带 tag,不能 :latest。

secrets 什么时候用:第三方凭证(私有 npm 源 token、内部制品库账号)才需要传。注意容器内引用名带前缀——传 { name: 'NPM_TOKEN', value: '...' },步骤里引用的是 $SECRET_NPM_TOKEN,不是 $NPM_TOKEN;引用了没传的变量不会报参数错误,跑到那一步才以空值失败。

例 3:前后端一体(一键部署)​

deployApp 默认是单服务(例 1 / 例 2),serviceList 把它扩展为多服务:一个应用名下挂多个服务,routes 把路径映射到各服务。一次调用,所有服务进同一个版本历史:

// 0. 上传源码(同例 2:getUploadUrl 带 deployType: 'custom' → PUT zip)

await manageApps({
action: 'deployApp',
serviceName: 'crm-fullstack', // 一个应用名,前后端同属一个版本序列
deployType: 'custom',
source: { type: 'zip', cosTimestamp: unixTimestamp }, // 同例 1:getUploadUrl 返回的 unixTimestamp
serviceList: [ // 不传 = 单服务(例 1 / 例 2)
{ serviceType: 'static-hosting', serviceName: 'web',
buildContext: { path: 'frontend', outPut: 'dist' }, // 该服务的构建目录与产物目录
command: { installCmd: 'npm ci', buildCmd: 'npm run build' } },
{ serviceType: 'http-function', serviceName: 'api',
buildContext: { path: 'backend' } },
],
routes: [ // 不传 = 走默认路由
{ source: '/api', serviceType: 'http-function', serviceName: 'api' },
{ source: '/', serviceType: 'static-hosting', serviceName: 'web' },
],
});
// 轮询与日志同例 1(getAppVersion / getBuildLog)

字段对照:serviceType 枚举 static-hosting / http-function / function;routes[].source 是路径(如 /api),targetPath 暂不支持。

正式发布:单服务应用把配置写进 cloudbaserc.json 进仓库,发布流水线自动执行部署;多服务(serviceList)目前没有声明式对应字段,流水线里用同样参数调 manager SDK 或 CLI 脚本。部署配置都可以让 AI 在对话里组装,进 git 走评审。

给 AI 的知识提示:模型默认不会声明式发布,也写不出 cloudbaserc.json。让 AI 自助配流水线,需要给它两份知识:配置规范(v2.1)和声明式部署——skill 的「声明式部署最小知识」即最小集。

版本语义​

同一个 serviceName 重复部署是新增版本不是覆盖,对话触发的试部署和流水线触发的正式发布互不踩,版本历史连续。前端资源用 app 不用 hosting:CloudApp 一个 serviceName 一个独立子域名,hosting 是共享环境域名靠子路径区分,容易互相覆盖。

第五步:验证​

用标准 MCP 客户端(假凭证 + 跳过自检)验证协议层,用真凭证验证一次作用域:

  1. initialize 握手成功(stdout 没被日志污染)
  2. tools/list 与插件组合一致
  3. 拦截表动作全部返回 isError: true
  4. auth(action="status") 返回 credential_scope=single_env——看到 account 说明回落到了全局登录态,停下排查
  5. 真环境部署一次,确认域名可访问

贯穿所有步骤的一条硬要求:接口名、参数、默认值、行为,一律以文档与实测为准,不从记忆推断。拿不准的内容先查API 索引与官方文档,推断值必须标注并验证后再用——这条对 AI 同样生效,把它写进给 AI 的指令里。

远端形态的差异​

wrapper 代码同源:工厂创建、插件裁剪、危险动作拦截、审计逻辑全部复用,变的只有四处:

差异点本地远端经验
传输StdioServerTransportStreamableHTTPServerTransport,短连接(无状态)模式:每请求独立处理,无需会话保持每请求新建一对 server 实例与传输——SDK 硬约束:一个实例同时只能绑一个 transport,并发复用会报「Already connected to a transport」;短连接下每请求新建即可,天然满足
工具面自己裁(pluginsEnabled + 过滤)开 cloudMode: true,官方自动不注册依赖本地文件的工具(声明式部署、本地上传),部分动作受限远端工具黑名单不用自己维护,官方内置
员工 → wrapper无鉴权(本机进程)企业 SSO / OAuth 网关网关层鉴权后置换凭证,员工全程零凭证
wrapper → CloudBaseAPI Key(env 注入,MCP 内部换临时凭证)OAuth 2.1(浏览器完成登录、免密钥)或静态凭证 header(X-TencentCloud-SecretId / X-TencentCloud-SecretKey,两个 header 位,无 STS Token 位置)OAuth 适合交互式客户端,header 适合服务端到服务端

启用方式:createCloudBaseMcpServer({ cloudMode: true, ... }),传输换 HTTP,其余逻辑不变。

真要做时的起步清单(现有材料能支撑的部分):

  1. 骨架:第二步的 wrapper 代码原样保留,StdioServerTransport 换成 StreamableHTTPServerTransport(短连接模式),每请求新建一对传输与实例即可
  2. 工具面:加 cloudMode: true,官方自动收窄;自己的 tools/list 过滤逻辑保留做企业级裁剪
  3. 鉴权:先定员工 → wrapper 的通道(企业 SSO / OAuth 网关),再定 wrapper → CloudBase 的通道(OAuth 2.1 或静态 header)
  4. 验证参照:官方托管的远端 MCP 端点(tcb-api.cloud.tencent.com/mcp/v1)是行为对照物——它的工具面、鉴权流、错误语义就是远端形态的既定事实

短连接模式下不需要会话管理——没有连接生命周期、断线重连、会话粘性这些问题,多实例横向扩容也没有状态负担。只有当需要服务端长连接会话(如 SSE 流式推送)时才引入会话层,届时再补实现。

进阶​

  • 危险动作的完整拦截表:官方二次确认清单可整体作为黑名单(函数删除、集合删除、MySQL 销毁、托管删除等),见连接方式 · 危险操作
  • 更细的权限:API Key 白名单模式(能力 × 读写)+ wrapper 工具过滤双层配合;其他凭证方式(授权码、STS)见其他凭证方式
  • 预留能力:deployType 有 http-function 预留值,开放后函数型部署可走同一套 CloudApp 管理