@@ -10,7 +10,7 @@ import { jsonContent } from "../utils/json-content.js";
1010import { debug } from "../utils/logger.js" ;
1111
1212import { IEnvVariable } from "@cloudbase/manager-node/types/function/types.js" ;
13- import { existsSync } from "fs" ;
13+ import { existsSync , readFileSync , statSync } from "fs" ;
1414import path from "path" ;
1515
1616export const SUPPORTED_RUNTIMES = {
@@ -233,7 +233,12 @@ const TRIGGER_SCHEMA = z.object({
233233
234234const CREATE_FUNCTION_SCHEMA = z . object ( {
235235 name : z . string ( ) . describe ( "函数名称" ) ,
236- type : z . enum ( [ "Event" , "HTTP" ] ) . optional ( ) . describe ( "函数类型" ) ,
236+ type : z
237+ . enum ( [ "Event" , "HTTP" ] )
238+ . optional ( )
239+ . describe (
240+ "函数类型。Event 函数使用 exports.main(event, context) 这类 handler 模式;HTTP 函数使用 scf_bootstrap 启动 Web 服务并监听 9000 端口,不要把两种模式混用。" ,
241+ ) ,
237242 protocolType : z . enum ( [ "HTTP" , "WS" ] ) . optional ( ) . describe ( "HTTP 云函数协议类型" ) ,
238243 protocolParams : z
239244 . object ( {
@@ -267,7 +272,12 @@ const CREATE_FUNCTION_SCHEMA = z.object({
267272 ` Go: ${ RECOMMENDED_RUNTIMES . golang } ` ,
268273 ) ,
269274 triggers : z . array ( TRIGGER_SCHEMA ) . optional ( ) . describe ( "触发器配置数组" ) ,
270- handler : z . string ( ) . optional ( ) . describe ( "函数入口" ) ,
275+ handler : z
276+ . string ( )
277+ . optional ( )
278+ . describe (
279+ "函数入口。Event 函数使用 file.export 格式(如 index.main),表示 index.js 文件导出的 main 方法;HTTP 函数默认不要传 handler,运行时由 scf_bootstrap 启动。把 HTTP Web 服务代码和 handler 混用,容易出现部署成功但运行时报 FUNCTION_EXECUTE_FAIL。" ,
280+ ) ,
271281 ignore : z . union ( [ z . string ( ) , z . array ( z . string ( ) ) ] ) . optional ( ) . describe ( "忽略文件" ) ,
272282 isWaitInstall : z . boolean ( ) . optional ( ) . describe ( "是否等待依赖安装" ) ,
273283 layers : z
@@ -327,6 +337,48 @@ function getExpectedFunctionPath(
327337 return path . join ( path . normalize ( functionRootPath ) , functionName ) ;
328338}
329339
340+ export function validateHttpFunctionCreateInput (
341+ functionName : string ,
342+ functionRootPath : string | undefined ,
343+ zipFile : string | undefined ,
344+ handler : unknown ,
345+ ) : void {
346+ if ( typeof handler === "string" && handler . trim ( ) ) {
347+ throw new Error (
348+ `createFunction 创建 HTTP 函数时,默认不要传 func.handler。HTTP 函数由 scf_bootstrap 启动,而不是 Event 函数的 handler 入口。请删除 func.handler,并确保 cloudfunctions/${ functionName } /scf_bootstrap 启动你的 HTTP 服务且监听 9000 端口。` ,
349+ ) ;
350+ }
351+
352+ if ( ! functionRootPath || zipFile ) {
353+ return ;
354+ }
355+
356+ const expectedFunctionPath = getExpectedFunctionPath ( functionRootPath , functionName ) ;
357+ if ( ! expectedFunctionPath ) {
358+ return ;
359+ }
360+
361+ const scfBootstrapPath = path . join ( expectedFunctionPath , "scf_bootstrap" ) ;
362+ if ( ! existsSync ( scfBootstrapPath ) ) {
363+ throw new Error (
364+ `createFunction 创建 HTTP 函数时,函数目录 ${ expectedFunctionPath } 下必须包含 scf_bootstrap 启动脚本。HTTP 函数由 scf_bootstrap 启动并监听 9000 端口,不能只依赖 handler。` ,
365+ ) ;
366+ }
367+
368+ const bootstrapContent = readFileSync ( scfBootstrapPath , "utf8" ) ;
369+ if ( bootstrapContent . includes ( "\r" ) ) {
370+ throw new Error (
371+ `createFunction 创建 HTTP 函数时,${ scfBootstrapPath } 不能使用 CRLF 行尾。请改为 LF 后重试,否则部署成功后仍可能因为启动脚本格式不兼容而运行失败。` ,
372+ ) ;
373+ }
374+
375+ if ( ( statSync ( scfBootstrapPath ) . mode & 0o111 ) === 0 ) {
376+ throw new Error (
377+ `createFunction 创建 HTTP 函数时,${ scfBootstrapPath } 必须有可执行权限。请先执行 chmod +x scf_bootstrap 后重试。` ,
378+ ) ;
379+ }
380+ }
381+
330382export function shouldInstallDependencyForFunction (
331383 functionType : string | undefined ,
332384 hasPackageJson : boolean ,
@@ -391,6 +443,18 @@ export function buildFunctionOperationErrorMessage(
391443 ) ;
392444 }
393445
446+ if ( / B o o t s t r a p F i l e | E n t r y f i l e | s c f _ b o o t s t r a p | e x e c f o r m a t e r r o r / i. test ( baseMessage ) ) {
447+ suggestions . push (
448+ "请确认 HTTP 函数目录下存在精确命名的 scf_bootstrap 启动脚本,并且文件使用 LF 行尾。" ,
449+ ) ;
450+ suggestions . push (
451+ "请确认 scf_bootstrap 已执行 chmod +x,且它真正启动的是监听 9000 端口的 HTTP 服务。" ,
452+ ) ;
453+ suggestions . push (
454+ "HTTP 函数默认不要再传 handler;运行时由 scf_bootstrap 启动,混入 Event handler 容易导致部署成功但运行失败。" ,
455+ ) ;
456+ }
457+
394458 if ( suggestions . length === 0 ) {
395459 suggestions . push ( "请检查函数名、目录结构和环境中的函数状态后重试。" ) ;
396460 }
@@ -896,6 +960,15 @@ export function registerFunctionTools(server: ExtendedMcpServer) {
896960 ) ;
897961 }
898962
963+ if ( functionType === "HTTP" ) {
964+ validateHttpFunctionCreateInput (
965+ functionName ,
966+ processedRootPath ,
967+ input . zipFile ,
968+ func . handler ,
969+ ) ;
970+ }
971+
899972 const hasPackageJson =
900973 expectedFunctionPath !== undefined
901974 ? existsSync ( path . join ( expectedFunctionPath , "package.json" ) )
@@ -942,6 +1015,12 @@ export function registerFunctionTools(server: ExtendedMcpServer) {
9421015 ] ;
9431016
9441017 if ( func . type === "HTTP" ) {
1018+ nextActions . push ( {
1019+ tool : "queryFunctions" ,
1020+ action : "listFunctionLogs" ,
1021+ reason :
1022+ "HTTP 函数创建成功只表示代码已上传;交付前请先检查启动日志,确认没有 FUNCTION_EXECUTE_FAIL、scf_bootstrap 或 Entryfile 相关错误" ,
1023+ } ) ;
9451024 nextActions . push ( {
9461025 tool : "manageGateway" ,
9471026 action : "createAccess" ,
@@ -969,7 +1048,7 @@ export function registerFunctionTools(server: ExtendedMcpServer) {
9691048
9701049 const message =
9711050 func . type === "HTTP"
972- ? `已创建 HTTP 函数 ${ functionName } 。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createAccess") 按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${ functionName } ") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
1051+ ? `已创建 HTTP 函数 ${ functionName } 。注意:创建成功只表示代码包已上传,不代表 HTTP 启动链路已经验证通过。HTTP 函数由 scf_bootstrap 启动,请在交付前至少调用 queryFunctions(action="listFunctionLogs", functionName=" ${ functionName } ") 或实际访问入口,确认没有 FUNCTION_EXECUTE_FAIL、scf_bootstrap 或 Entryfile 相关错误。 如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createAccess") 按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${ functionName } ") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
9731052 : `已创建函数 ${ functionName } ` ;
9741053
9751054 return buildEnvelope (
@@ -1442,16 +1521,21 @@ export function registerFunctionTools(server: ExtendedMcpServer) {
14421521 func : CREATE_FUNCTION_SCHEMA . optional ( ) . describe ( "createFunction 操作的函数配置" ) ,
14431522 functionRootPath : z . string ( ) . optional ( ) . describe (
14441523 "创建或更新函数代码时默认推荐的本地目录方式。函数根目录(父目录绝对路径)。" +
1445- "本地应按 cloudfunctions/<functionName>/index.js 布局," +
1446- "此参数传 cloudfunctions 目录的绝对路径(如 /abs/path/cloudfunctions),不要传到函数名子目录。" +
1447- "SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。" ,
1524+ "本地应按 cloudfunctions/<functionName>/index.js 布局," +
1525+ "此参数传 cloudfunctions 目录的绝对路径(如 /abs/path/cloudfunctions),不要传到函数名子目录。" +
1526+ "SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。" +
1527+ "如果 createFunction 的 func.type=HTTP,则对应函数子目录还必须包含 scf_bootstrap,且启动脚本要使用 LF 行尾并具备可执行权限。" ,
14481528 ) ,
1529+
14491530 force : z . boolean ( ) . optional ( ) . describe ( "createFunction 时是否覆盖" ) ,
14501531 functionName : z . string ( ) . optional ( ) . describe ( "函数名称。大多数 action 使用该字段作为统一目标" ) ,
14511532 zipFile : z . string ( ) . optional ( ) . describe (
14521533 "仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。" ,
14531534 ) ,
1454- handler : z . string ( ) . optional ( ) . describe ( "函数入口" ) ,
1535+ handler : z . string ( ) . optional ( ) . describe (
1536+ "函数入口。Event 函数使用 file.export 格式(如 index.main);" +
1537+ "HTTP 函数不需要 handler,由 scf_bootstrap 决定入口,请不要为 HTTP 函数设置此字段。" ,
1538+ ) ,
14551539 timeout : z . number ( ) . optional ( ) . describe ( "配置更新时的超时时间" ) ,
14561540 envVariables : z . record ( z . string ( ) ) . optional ( ) . describe ( "配置更新时要合并的环境变量" ) ,
14571541 vpc : VPC_SCHEMA . optional ( ) . describe ( "配置更新时的 VPC 信息" ) ,
0 commit comments