@cloudbase/cloudbase-mcp
Version:
腾讯云开发 MCP Server,通过AI提示词和MCP协议+云开发,让开发更智能、更高效,当你在Cursor/ VSCode GitHub Copilot/WinSurf/CodeBuddy/Augment Code/Claude Code等AI编程工具里写代码时,它能自动帮你生成可直接部署的前后端应用+小程序,并一键发布到腾讯云开发 CloudBase。
1 lines • 188 kB
JavaScript
import*as e from"@cloudbase/toolbox";import*as n from"os";import*as t from"@cloudbase/cals/lib/cjs/utils/mermaid-datasource/mermaid-json-transform";import*as o from"ws";import*as r from"open";import*as a from"net";import*as i from"fs";import*as s from"@modelcontextprotocol/sdk/server/stdio.js";import*as c from"path";import*as l from"@modelcontextprotocol/sdk/server/mcp.js";import*as d from"@cloudbase/manager-node";import*as p from"express";import*as u from"http";import*as g from"dns";import*as m from"crypto";import*as f from"https";import*as b from"adm-zip";import*as h from"url";import*as y from"fs/promises";import*as v from"zod";var x,w,I={39:function(e,n,t){var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(n,"__esModule",{value:!0}),n.clearLogs=n.getLoggerStatus=n.getLogs=n.error=n.warn=n.info=n.debug=n.logger=n.LogLevel=void 0;const r=o(t(932)),a=o(t(521)),i=o(t(116));var s;!function(e){e[e.DEBUG=0]="DEBUG",e[e.INFO=1]="INFO",e[e.WARN=2]="WARN",e[e.ERROR=3]="ERROR"}(s||(n.LogLevel=s={})),n.logger=new class{enabled;level;logFile;useConsole;constructor(e={}){this.enabled=e.enabled??!0,this.level=e.level??s.INFO,this.useConsole=e.console??!1,this.logFile=e.logFile??a.default.join(i.default.tmpdir(),"cloudbase-mcp.log")}async writeLog(e,n,t){if(!this.enabled||e<this.level)return;const o=(new Date).toISOString(),a=s[e],i=t?`[${o}] [${a}] ${n} ${JSON.stringify(t,null,2)}`:`[${o}] [${a}] ${n}`;if(this.useConsole&&console.error(i),this.logFile)try{await r.default.appendFile(this.logFile,i+"\n")}catch(e){}}debug(e,n){this.writeLog(s.DEBUG,e,n)}info(e,n){this.writeLog(s.INFO,e,n)}warn(e,n){this.writeLog(s.WARN,e,n)}error(e,n){this.writeLog(s.ERROR,e,n)}setLevel(e){this.level=e}setEnabled(e){this.enabled=e}getLogFile(){return this.logFile}async clearLogs(){if(this.logFile)try{await r.default.writeFile(this.logFile,"")}catch(e){}}async getLogs(e=1e3){if(!this.logFile)return[];try{return(await r.default.readFile(this.logFile,"utf-8")).split("\n").filter(e=>e.trim()).slice(-e)}catch(e){return[`读取日志文件失败: ${e instanceof Error?e.message:String(e)}`]}}getStatus(){return{enabled:this.enabled,level:s[this.level],logFile:this.logFile,useConsole:this.useConsole}}}({enabled:"true"===(process.env.MCP_DEBUG??"true"),level:s.DEBUG,console:"true"===process.env.MCP_CONSOLE_LOG}),n.debug=(e,t)=>n.logger.debug(e,t),n.info=(e,t)=>n.logger.info(e,t),n.warn=(e,t)=>n.logger.warn(e,t),n.error=(e,t)=>n.logger.error(e,t),n.getLogs=e=>n.logger.getLogs(e),n.getLoggerStatus=()=>n.logger.getStatus(),n.clearLogs=()=>n.logger.clearLogs()},90:n=>{n.exports=e},97:function(e,n,t){var o,r=this&&this.__createBinding||(Object.create?function(e,n,t,o){void 0===o&&(o=t);var r=Object.getOwnPropertyDescriptor(n,t);r&&!("get"in r?!n.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return n[t]}}),Object.defineProperty(e,o,r)}:function(e,n,t,o){void 0===o&&(o=t),e[o]=n[t]}),a=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),i=this&&this.__importStar||(o=function(e){return o=Object.getOwnPropertyNames||function(e){var n=[];for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[n.length]=t);return n},o(e)},function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var t=o(e),i=0;i<t.length;i++)"default"!==t[i]&&r(n,e,t[i]);return a(n,e),n});Object.defineProperty(n,"__esModule",{value:!0}),n.registerDownloadTools=function(e){e.registerTool("downloadRemoteFile",{title:"下载远程文件",description:"下载远程文件到本地临时文件,返回一个系统的绝对路径",inputSchema:{url:s.z.string().describe("远程文件的 URL 地址")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0,category:"download"}},async({url:e})=>{try{const n=await function(e){return new Promise((n,t)=>{(e.startsWith("https:")?g:m).get(e,async o=>{if(200!==o.statusCode)return void t(new Error(`HTTP Error: ${o.statusCode}`));const r=o.headers["content-type"]||"",a=parseInt(o.headers["content-length"]||"0",10),i=o.headers["content-disposition"];if(!await async function(e,n){try{const t=new f.URL(e);if(!v.includes(t.protocol))return!1;const o=t.hostname;return(!b.isIP(o)||!w(o))&&!(!b.isIP(o)&&await async function(e){try{const n=await new Promise((n,t)=>{h.resolve(e,(e,o)=>{e?t(e):n(o)})});return n.some(e=>w(e))}catch(e){return!0}}(o))&&x.some(e=>n.startsWith(e))}catch{return!1}}(e,r))return void t(new Error("不安全的 URL 或内容类型,或者目标为内网地址"));if(a>y)return void t(new Error(`文件大小 ${a} 字节超过 ${y} 字节限制`));const s=function(e,n,t){let o="";const r=new f.URL(e).pathname,a=d.extname(r);if(a&&(o=a),t){const e=t.match(/filename=["']?([^"']+)["']?/);if(e){const n=d.extname(e[1]);n&&(o=n)}}return!o&&n&&(o={"text/plain":".txt","text/html":".html","text/css":".css","text/javascript":".js","image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","application/json":".json","application/xml":".xml","application/pdf":".pdf","application/zip":".zip","application/x-zip-compressed":".zip"}[n]||""),o}(e,r,i),g=function(e=""){return`${u.randomBytes(16).toString("hex")}${e}`}(s),m=function(e){return d.join(p.tmpdir(),e)}(g),I=l.createWriteStream(m);let S=0;o.on("data",e=>{S+=e.length,S>y&&(I.destroy(),c.unlink(m).catch(()=>{}),t(new Error(`文件大小超过 ${y} 字节限制`)))}),o.pipe(I),I.on("finish",()=>{n({filePath:m,contentType:r,fileSize:S})}),I.on("error",e=>{c.unlink(m).catch(()=>{}),t(e)})}).on("error",e=>{t(e)})})}(e);return{content:[{type:"text",text:JSON.stringify({success:!0,filePath:n.filePath,contentType:n.contentType,fileSize:n.fileSize,message:"文件下载成功"},null,2)}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({success:!1,error:e.message,message:"文件下载失败"},null,2)}]}}})};const s=t(971),c=i(t(932)),l=i(t(421)),d=i(t(521)),p=i(t(116)),u=i(t(823)),g=i(t(871)),m=i(t(782)),f=t(887),b=i(t(357)),h=i(t(793)),y=104857600,v=["http:","https:"],x=["text/","image/","application/json","application/xml","application/pdf","application/zip","application/x-zip-compressed"];function w(e){if(!b.isIP(e))return!0;if("127.0.0.1"===e||"localhost"===e||"::1"===e||e.startsWith("169.254.")||e.startsWith("0."))return!0;const n=e.split(".").map(e=>parseInt(e,10));if(4===n.length){const e=(n[0]<<24)+(n[1]<<16)+(n[2]<<8)+n[3];if(e>=167772160&&e<=184549375)return!0;if(e>=2886729728&&e<=2887778303)return!0;if(e>=3232235520&&e<=3232301055)return!0}if(b.isIPv6(e)){const n=e.toLowerCase();if(n.startsWith("fc00:")||n.startsWith("fe80:")||n.startsWith("fec0:")||n.startsWith("::1"))return!0}return!1}},116:e=>{e.exports=n},191:e=>{e.exports=t},215:(e,n,t)=>{Object.defineProperty(n,"__esModule",{value:!0}),n.registerRagTools=function(e){e.registerTool?.("searchWeb",{title:"联网搜索",description:"使用联网来进行信息检索,如查询最新的新闻、文章、股价、天气等。支持自然语言查询,也可以直接输入网址获取网页内容",inputSchema:{query:o.z.string().describe("搜索关键词、问题或网址,支持自然语言")},annotations:{readOnlyHint:!0,openWorldHint:!0,category:"web"}},async({query:e})=>{try{const n=await fetch("https://tcb-advanced-a656fc.api.tcloudbasegateway.com/auth/v1/signin/anonymously",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","x-device-id":"cloudbase-ai-toolkit"},body:s({})}),{access_token:t}=await n.json(),o=await fetch("https://tcb-advanced-a656fc.api.tcloudbasegateway.com/v1/ai/hunyuan-beta/openapi/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:s({model:"hunyuan",messages:[{role:"user",content:"你好,你将扮演一个知识库的角色,联网查询到问题的答案之后回答给我"},{role:"assistant",content:"好的"},{role:"user",content:e}],enable_enhancement:!0,search_info:!0,enable_speed_search:!0,force_search_enhancement:!0})}),r=await o.json();if(r.error)throw new Error(r.error.message||"联网搜索失败");return{content:[{type:"text",text:s({type:"web_search",content:r.choices?.[0]?.message?.content||"",search_results:(r.search_info?.search_results||[]).map(e=>({title:e.title,url:e.url,snippet:e.snippet})),status:"success"})}]}}catch(e){return{content:[{type:"text",text:s({type:"error",message:e instanceof Error?e.message:"联网搜索失败",status:"error"})}]}}}),e.registerTool?.("searchKnowledgeBase",{title:"云开发知识库检索",description:"云开发知识库智能检索工具,支持云开发与云函数知识的向量查询",inputSchema:{threshold:o.z.number().default(.5).optional().describe("相似性检索阈值"),id:r.describe("知识库范围,cloudbase=云开发全量知识,scf=云开发的云函数知识, miniprogram=小程序知识(不包含云开发与云函数知识)"),content:o.z.string().describe("检索内容"),options:o.z.object({chunkExpand:o.z.array(o.z.number()).min(2).max(2).default([3,3]).describe("指定返回的文档内容的展开长度,例如 [3,3]代表前后展开长度")}).optional().describe("其他选项"),limit:o.z.number().default(5).optional().describe("指定返回最相似的 Top K 的 K 的值")},annotations:{readOnlyHint:!0,openWorldHint:!0,category:"rag"}},async({id:e,content:n,options:{chunkExpand:t=[3,3]}={},limit:o=5,threshold:r=.5})=>{const c=a[e]||e,l=await fetch("https://tcb-advanced-a656fc.api.tcloudbasegateway.com/auth/v1/signin/anonymously",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","x-device-id":"cloudbase-ai-toolkit"},body:s({collectionView:c,options:{chunkExpand:t},search:{content:n,limit:o}})}),d=(await l.json()).access_token,p=await fetch("https://tcb-advanced-a656fc.api.tcloudbasegateway.com/v1/knowledge/search",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${d}`},body:s({collectionView:c,options:{chunkExpand:t},search:{content:n,limit:o}})}),u=await p.json();if(u.code)throw new Error(u.message);return{content:[{type:"text",text:s(u.data.documents.filter(e=>e.score>=r).map(e=>({score:e.score,fileTile:e.documentSet.fileTitle,url:i(e.documentSet.fileMetaData).url,paragraphTitle:e.data.paragraphTitle,text:`${e.data.pre?.join("\n")||""}\n ${e.data.text}\n ${e.data.next?.join("\n")||""}`})))}]}})};const o=t(971),r=o.z.enum(["cloudbase","scf","miniprogram"]),a={cloudbase:"ykfzskv4_ad28",scf:"scfsczskzyws_4bdc",miniprogram:"xcxzskws_25d8"};function i(e){try{return JSON.parse(e)}catch(e){return{}}}function s(e){const n=new WeakSet;try{return JSON.stringify(e,function(e,t){if("object"==typeof t&&null!==t){if(n.has(t))return;n.add(t)}return t})}catch(e){return""}}},220:e=>{e.exports=o},241:e=>{e.exports=r},279:(e,n,t)=>{Object.defineProperty(n,"__esModule",{value:!0}),n.registerHostingTools=function(e){const n=e.cloudBaseOptions,t=()=>(0,r.getCloudBaseManager)({cloudBaseOptions:n});e.registerTool("uploadFiles",{title:"上传静态文件",description:"上传文件到静态网站托管",inputSchema:{localPath:o.z.string().optional().describe("本地文件或文件夹路径,需要是绝对路径,例如 /tmp/files/data.txt"),cloudPath:o.z.string().optional().describe("云端文件或文件夹路径,例如files/data.txt"),files:o.z.array(o.z.object({localPath:o.z.string(),cloudPath:o.z.string()})).default([]).describe("多文件上传配置"),ignore:o.z.union([o.z.string(),o.z.array(o.z.string())]).optional().describe("忽略文件模式")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0,category:"hosting"}},async({localPath:e,cloudPath:n,files:o=[],ignore:r})=>{const a=await t(),i=await a.hosting.uploadFiles({localPath:e,cloudPath:n,files:o,ignore:r}),s=await a.env.getEnvInfo(),c=s.EnvInfo?.StaticStorages?.[0]?.StaticDomain;return{content:[{type:"text",text:JSON.stringify({...i,staticDomain:c,message:"文件上传成功",accessUrl:c?`https://${c}/${n||""}`:"请检查静态托管配置"},null,2)}]}}),e.registerTool?.("getWebsiteConfig",{title:"查询静态托管配置",description:"获取静态网站托管配置",inputSchema:{},annotations:{readOnlyHint:!0,openWorldHint:!0,category:"hosting"}},async()=>{const e=await t(),n=await e.hosting.getWebsiteConfig();return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}),e.registerTool?.("deleteFiles",{title:"删除静态文件",description:"删除静态网站托管的文件或文件夹",inputSchema:{cloudPath:o.z.string().describe("云端文件或文件夹路径"),isDir:o.z.boolean().default(!1).describe("是否为文件夹")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0,category:"hosting"}},async({cloudPath:e,isDir:n=!1})=>{const o=await t(),r=await o.hosting.deleteFiles({cloudPath:e,isDir:n});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}),e.registerTool?.("findFiles",{title:"搜索静态文件",description:"搜索静态网站托管的文件",inputSchema:{prefix:o.z.string().describe("匹配前缀"),marker:o.z.string().optional().describe("起始对象键标记"),maxKeys:o.z.number().optional().describe("单次返回最大条目数")},annotations:{readOnlyHint:!0,openWorldHint:!0,category:"hosting"}},async({prefix:e,marker:n,maxKeys:o})=>{const r=await t(),a=await r.hosting.findFiles({prefix:e,marker:n,maxKeys:o});return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}),e.registerTool?.("domainManagement",{title:"静态托管域名管理",description:"统一的域名管理工具,支持绑定、解绑、查询和修改域名配置",inputSchema:{action:o.z.enum(["create","delete","check","modify"]).describe("操作类型: create=绑定域名, delete=解绑域名, check=查询域名配置, modify=修改域名配置"),domain:o.z.string().optional().describe("域名"),certId:o.z.string().optional().describe("证书ID(绑定域名时必需)"),domains:o.z.array(o.z.string()).optional().describe("域名列表(查询配置时使用)"),domainId:o.z.number().optional().describe("域名ID(修改配置时必需)"),domainConfig:o.z.object({Refer:o.z.object({Switch:o.z.string(),RefererRules:o.z.array(o.z.object({RefererType:o.z.string(),Referers:o.z.array(o.z.string()),AllowEmpty:o.z.boolean()})).optional()}).optional(),Cache:o.z.array(o.z.object({RuleType:o.z.string(),RuleValue:o.z.string(),CacheTtl:o.z.number()})).optional(),IpFilter:o.z.object({Switch:o.z.string(),FilterType:o.z.string().optional(),Filters:o.z.array(o.z.string()).optional()}).optional(),IpFreqLimit:o.z.object({Switch:o.z.string(),Qps:o.z.number().optional()}).optional()}).optional().describe("域名配置(修改配置时使用)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0,category:"hosting"}},async({action:e,domain:n,certId:o,domains:r,domainId:a,domainConfig:i})=>{const s=await t();let c;switch(e){case"create":if(!n||!o)throw new Error("绑定域名需要提供域名和证书ID");c=await s.hosting.CreateHostingDomain({domain:n,certId:o});break;case"delete":if(!n)throw new Error("解绑域名需要提供域名");c=await s.hosting.deleteHostingDomain({domain:n});break;case"check":if(!r||0===r.length)throw new Error("查询域名配置需要提供域名列表");c=await s.hosting.tcbCheckResource({domains:r});break;case"modify":if(!n||void 0===a||!i)throw new Error("修改域名配置需要提供域名、域名ID和配置信息");c=await s.hosting.tcbModifyAttribute({domain:n,domainId:a,domainConfig:i});break;default:throw new Error(`不支持的操作类型: ${e}`)}return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}})};const o=t(971),r=t(431)},291:(e,n,t)=>{Object.defineProperty(n,"__esModule",{value:!0}),n.getLoginState=async function(){const{TENCENTCLOUD_SECRETID:e,TENCENTCLOUD_SECRETKEY:n,TENCENTCLOUD_SESSIONTOKEN:t}=process.env;if((0,r.debug)("TENCENTCLOUD_SECRETID",e),e&&n)return(0,r.debug)("loginByApiSecret"),{secretId:e,secretKey:n,token:t};const o=await a.getLoginState();return o||((0,r.debug)("loginByApiSecret"),await a.loginByWebAuth({}),await a.getLoginState())},n.logout=async function(){return await a.logout()};const o=t(90),r=t(39),a=o.AuthSupevisor.getInstance({})},319:(e,n,t)=>{Object.defineProperty(n,"__esModule",{value:!0}),n.registerGatewayTools=function(e){const n=e.cloudBaseOptions;e.registerTool?.("createFunctionHTTPAccess",{title:"创建云函数HTTP访问",description:"创建云函数的 HTTP 访问",inputSchema:{name:o.z.string().describe("函数名"),path:o.z.string().describe("HTTP 访问路径")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0,category:"gateway"}},async({name:e,path:t})=>{const o=await(0,r.getCloudBaseManager)({cloudBaseOptions:n}),a=await o.access.createAccess({type:1,name:e,path:t});return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}})};const o=t(971),r=t(431)},341:function(e,n,t){var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(n,"__esModule",{value:!0}),n.InteractiveServer=void 0,n.getInteractiveServer=u,n.resetInteractiveServer=async function(){if(p){try{await p.stop()}catch(e){(0,c.error)("Error stopping existing server instance:",e)}p=null}},n.getInteractiveServerSafe=async function(e){if(p&&!p.running){try{await p.stop()}catch(e){(0,c.debug)("Error stopping non-running server:",e)}p=null}return u(e)};const r=o(t(674)),a=o(t(782)),i=t(220),s=o(t(241)),c=t(39);async function l(e,n,t){if("CodeBuddy"===process.env.INTEGRATION_IDE&&t)try{return t.server.sendLoggingMessage({level:"notice",data:{type:"tcb",url:e}}),void(0,c.info)(`CodeBuddy IDE: 已发送网页打开通知 - ${e}`)}catch(n){(0,c.error)(`Failed to send logging message for ${e}: ${n instanceof Error?n.message:n}`,n),(0,c.warn)(`回退到直接打开网页: ${e}`)}try{return await(0,s.default)(e,n)}catch(t){(0,c.error)(`Failed to open ${e} ${n} ${t instanceof Error?t.message:t} `,t),(0,c.warn)(`Please manually open: ${e}`)}}class d{app;server;wss;port=0;isRunning=!1;currentResolver=null;sessionData=new Map;_mcpServer=null;get mcpServer(){return this._mcpServer}set mcpServer(e){this._mcpServer=e}DEFAULT_PORT=3721;FALLBACK_PORTS=[3722,3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735];constructor(e){this._mcpServer=e,this.app=(0,r.default)(),this.server=a.default.createServer(this.app),this.wss=new i.WebSocketServer({server:this.server}),this.setupExpress(),this.setupWebSocket(),process.on("exit",()=>this.cleanup()),process.on("SIGINT",()=>this.cleanup()),process.on("SIGTERM",()=>this.cleanup())}cleanup(){this.isRunning&&((0,c.debug)("Cleaning up interactive server resources..."),this.server.close(),this.wss.close(),this.isRunning=!1)}setupExpress(){this.app.use(r.default.json()),this.app.get("/env-setup/:sessionId",(e,n)=>{const{sessionId:t}=e.params,o=this.sessionData.get(t);o?n.send(this.getEnvSetupHTML(o.envs)):n.status(404).send("会话不存在或已过期")}),this.app.get("/clarification/:sessionId",(e,n)=>{const{sessionId:t}=e.params,o=this.sessionData.get(t);o?n.send(this.getClarificationHTML(o.message,o.options)):n.status(404).send("会话不存在或已过期")}),this.app.get("/debug/logs",async(e,n)=>{try{const e=await(0,c.getLogs)(1e3),t=(0,c.getLoggerStatus)();n.send(this.getLogsHTML(e,t))}catch(e){n.status(500).send("获取日志失败")}}),this.app.get("/api/logs",async(e,n)=>{try{const t=parseInt(e.query.maxLines)||1e3,o=await(0,c.getLogs)(t),r=(0,c.getLoggerStatus)();n.json({logs:o,status:r,success:!0})}catch(e){n.status(500).json({success:!1,error:"Failed to get logs"})}}),this.app.post("/api/logs/clear",async(e,n)=>{try{await(0,c.clearLogs)(),n.json({success:!0})}catch(e){n.status(500).json({success:!1,error:"Failed to clear logs"})}}),this.app.post("/api/submit",(e,n)=>{const{type:t,data:o}=e.body;(0,c.debug)("Received submit request",{type:t,data:o}),this.currentResolver?((0,c.info)("Resolving with user data"),this.currentResolver({type:t,data:o}),this.currentResolver=null):(0,c.warn)("No resolver waiting for response"),n.json({success:!0})}),this.app.post("/api/cancel",(e,n)=>{(0,c.info)("Received cancel request"),this.currentResolver?((0,c.info)("Resolving with cancelled status"),this.currentResolver({type:"clarification",data:null,cancelled:!0}),this.currentResolver=null):(0,c.warn)("No resolver waiting for cancellation"),n.json({success:!0})})}setupWebSocket(){this.wss.on("connection",e=>{(0,c.debug)("WebSocket client connected"),e.on("message",e=>{try{const n=JSON.parse(e.toString());(0,c.debug)("WebSocket message received",n),this.currentResolver&&(this.currentResolver(n),this.currentResolver=null)}catch(e){(0,c.error)("WebSocket message parsing error",e)}}),e.on("close",()=>{(0,c.debug)("WebSocket client disconnected")})})}async start(){return this.isRunning?((0,c.debug)(`Interactive server already running on port ${this.port}`),this.port):new Promise((e,n)=>{(0,c.info)("Starting interactive server...");const t=[this.DEFAULT_PORT,...this.FALLBACK_PORTS];let o=0;const r=()=>{if(o>=t.length){const e=new Error(`All ${t.length} ports are in use (${t.join(", ")}), failed to start server`);return(0,c.error)("Server start failed",e),void n(e)}const e=t[o];o++,(0,c.debug)(`Trying to start server on port ${e} (attempt ${o}/${t.length})`),a(e)},a=t=>{this.server.removeAllListeners("error"),this.server.removeAllListeners("listening");const o=e=>{"EADDRINUSE"===e.code?((0,c.warn)(`Port ${t} is in use, trying next port...`),this.server.removeAllListeners("error"),this.server.removeAllListeners("listening"),r()):((0,c.error)("Server error",e),n(e))},a=()=>{const t=this.server.address();if(t&&"object"==typeof t)this.port=t.port,this.isRunning=!0,(0,c.info)(`Interactive server started successfully on http://localhost:${this.port}`),this.server.removeListener("error",o),this.server.removeListener("listening",a),e(this.port);else{const e=new Error("Failed to get server address");(0,c.error)("Server start error",e),n(e)}};this.server.once("error",o),this.server.once("listening",a);try{this.server.listen(t,"127.0.0.1")}catch(e){(0,c.error)(`Failed to bind to port ${t}:`,e),r()}};r()})}async stop(){if(this.isRunning)return(0,c.info)("Stopping interactive server..."),new Promise((e,n)=>{const t=setTimeout(()=>{(0,c.warn)("Server close timeout, forcing cleanup"),this.isRunning=!1,this.port=0,e()},3e4);try{this.wss.close(()=>{(0,c.debug)("WebSocket server closed")}),this.server.close(o=>{clearTimeout(t),o?((0,c.error)("Error closing server:",o),n(o)):((0,c.info)("Interactive server stopped successfully"),this.isRunning=!1,this.port=0,e())})}catch(e){clearTimeout(t),(0,c.error)("Error stopping server:",e),this.isRunning=!1,this.port=0,n(e)}});(0,c.debug)("Interactive server is not running, nothing to stop")}async collectEnvId(e){try{(0,c.info)("Starting environment ID collection..."),(0,c.debug)(`Available environments: ${e.length}`);const n=await this.start(),t=Math.random().toString(36).substring(2,15);this.sessionData.set(t,{envs:e}),(0,c.debug)(`Created session: ${t}`),setTimeout(()=>{this.sessionData.delete(t),(0,c.debug)(`Session ${t} expired`)},3e5);const o=`http://localhost:${n}/env-setup/${t}`;(0,c.info)(`Opening browser: ${o}`);try{await l(o,{wait:!1},this._mcpServer),(0,c.info)("Browser opened successfully")}catch(e){(0,c.error)("Failed to open browser",e),(0,c.warn)(`Please manually open: ${o}`)}return(0,c.info)("Waiting for user selection..."),new Promise(e=>{this.currentResolver=n=>{this.stop().catch(e=>{(0,c.debug)("Error stopping server after user selection:",e)}),e(n)},setTimeout(()=>{this.currentResolver&&((0,c.warn)("Request timeout, resolving with cancelled"),this.currentResolver=null,this.stop().catch(e=>{(0,c.debug)("Error stopping server after timeout:",e)}),e({type:"envId",data:null,cancelled:!0}))},6e5)})}catch(e){throw(0,c.error)("Error in collectEnvId",e),e}}async clarifyRequest(e,n){const t=await this.start(),o=Math.random().toString(36).substring(2,15);this.sessionData.set(o,{message:e,options:n}),setTimeout(()=>{this.sessionData.delete(o)},3e5);const r=`http://localhost:${t}/clarification/${o}`;return await l(r,void 0,this._mcpServer),new Promise(e=>{this.currentResolver=n=>{this.stop().catch(e=>{(0,c.debug)("Error stopping server after user selection:",e)}),e(n)}})}getEnvSetupHTML(e){return`\n<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>CloudBase AI Toolkit - 环境配置</title>\n <style>\n @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');\n \n * { margin: 0; padding: 0; box-sizing: border-box; }\n :root {\n --primary-color: #1a1a1a;\n --primary-hover: #000000;\n --accent-color: #67E9E9;\n --accent-hover: #2BCCCC;\n --text-primary: #ffffff;\n --text-secondary: #a0a0a0;\n --border-color: rgba(255, 255, 255, 0.15);\n --bg-secondary: rgba(255, 255, 255, 0.08);\n --bg-glass: rgba(26, 26, 26, 0.95);\n --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);\n --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;\n --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);\n }\n \n body {\n font-family: var(--font-mono);\n background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n position: relative;\n overflow-x: hidden;\n overflow-y: auto;\n }\n \n /* Custom scrollbar styles */\n ::-webkit-scrollbar {\n width: 8px;\n }\n \n ::-webkit-scrollbar-track {\n background: rgba(255, 255, 255, 0.05);\n border-radius: 4px;\n }\n \n ::-webkit-scrollbar-thumb {\n background: var(--accent-color);\n border-radius: 4px;\n }\n \n ::-webkit-scrollbar-thumb:hover {\n background: var(--accent-hover);\n }\n \n body::before {\n content: '';\n position: fixed;\n top: 0; left: 0; right: 0; bottom: 0;\n background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat;\n pointer-events: none;\n z-index: -1;\n }\n \n body::after {\n content: '';\n position: fixed;\n top: 50%; left: 50%;\n width: 500px; height: 500px;\n background: radial-gradient(circle, rgba(103, 233, 233, 0.05) 0%, transparent 70%);\n transform: translate(-50%, -50%);\n pointer-events: none;\n z-index: -1;\n animation: pulse 8s ease-in-out infinite;\n }\n \n @keyframes pulse {\n 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }\n 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }\n }\n \n .modal {\n background: var(--bg-glass);\n backdrop-filter: blur(20px);\n border-radius: 20px;\n box-shadow: var(--shadow);\n border: 2px solid var(--border-color);\n width: 100%;\n max-width: 520px;\n overflow: hidden;\n animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);\n position: relative;\n }\n \n .modal::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0; bottom: 0;\n background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%);\n animation: shimmer 3s infinite;\n pointer-events: none;\n }\n \n @keyframes shimmer {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(100%); }\n }\n \n @keyframes modalIn {\n from {\n opacity: 0;\n transform: scale(0.9) translateY(-20px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n }\n \n .header {\n background: var(--header-bg);\n color: var(--text-primary);\n padding: 24px 28px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: relative;\n overflow: hidden;\n }\n \n .header::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0; bottom: 0;\n background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%);\n animation: headerShimmer 4s infinite;\n pointer-events: none;\n }\n \n @keyframes headerShimmer {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(100%); }\n }\n \n .header-left {\n display: flex;\n align-items: center;\n gap: 16px;\n z-index: 1;\n }\n \n .logo {\n width: 32px;\n height: 32px;\n filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));\n animation: logoFloat 3s ease-in-out infinite;\n }\n \n @keyframes logoFloat {\n 0%, 100% { transform: translateY(0px); }\n 50% { transform: translateY(-3px); }\n }\n \n .title {\n font-size: 20px;\n font-weight: 700;\n text-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n \n .github-link {\n color: var(--text-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(10px);\n padding: 8px 16px;\n border-radius: 8px;\n font-weight: 500;\n z-index: 1;\n transition: all 0.3s ease;\n }\n \n .github-link:hover {\n background: rgba(255,255,255,0.15);\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.3);\n }\n \n .content {\n padding: 32px 24px;\n position: relative;\n }\n \n .content-title {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 8px;\n animation: fadeInUp 0.8s ease-out 0.2s both;\n }\n \n .content-subtitle {\n color: var(--text-secondary);\n margin-bottom: 24px;\n line-height: 1.5;\n animation: fadeInUp 0.8s ease-out 0.4s both;\n }\n \n @keyframes fadeInUp {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n \n .env-list {\n border: 1px solid var(--border-color);\n border-radius: 12px;\n margin-bottom: 24px;\n max-height: 300px;\n overflow-y: auto;\n overflow-x: hidden;\n background: rgba(255, 255, 255, 0.03);\n animation: fadeInUp 0.8s ease-out 0.6s both;\n }\n \n .env-item {\n padding: 16px 20px;\n border-bottom: 1px solid var(--border-color);\n cursor: pointer;\n transition: all 0.3s ease;\n display: flex;\n align-items: center;\n gap: 14px;\n position: relative;\n overflow: hidden;\n color: var(--text-primary);\n }\n \n .env-item::before {\n content: '';\n position: absolute;\n left: 0; top: 0; bottom: 0;\n width: 0;\n background: var(--accent-color);\n transition: width 0.3s ease;\n }\n \n .env-item:last-child {\n border-bottom: none;\n }\n \n .env-item:hover {\n background: var(--bg-secondary);\n transform: translateX(5px);\n }\n \n .env-item:hover::before {\n width: 4px;\n }\n \n .env-item.selected {\n background: rgba(103, 233, 233, 0.1);\n border-left: 4px solid var(--accent-color);\n transform: translateX(5px);\n }\n \n .env-icon {\n width: 20px;\n height: 20px;\n color: var(--accent-color);\n flex-shrink: 0;\n animation: iconGlow 2s ease-in-out infinite;\n }\n \n @keyframes iconGlow {\n 0%, 100% { filter: drop-shadow(0 0 2px rgba(103, 233, 233, 0.3)); }\n 50% { filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.6)); }\n }\n \n .env-info {\n flex: 1;\n }\n \n .env-name {\n font-weight: 600;\n color: var(--text-primary);\n margin-bottom: 4px;\n }\n \n .env-alias {\n color: var(--text-secondary);\n font-size: 14px;\n }\n \n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n text-align: center;\n animation: fadeIn 0.8s ease-out;\n }\n \n .empty-icon {\n margin-bottom: 24px;\n color: var(--text-secondary);\n opacity: 0.6;\n }\n \n .empty-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-primary);\n margin-bottom: 12px;\n }\n \n .empty-message {\n font-size: 14px;\n color: var(--text-secondary);\n line-height: 1.6;\n margin-bottom: 32px;\n max-width: 400px;\n }\n \n .create-env-btn {\n padding: 14px 24px;\n font-size: 15px;\n background: var(--primary-color);\n color: var(--text-primary);\n border: 1px solid var(--border-color);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n }\n \n .create-env-btn:hover {\n background: var(--primary-hover);\n transform: translateY(-2px);\n box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);\n }\n \n .actions {\n display: flex;\n gap: 12px;\n justify-content: flex-end;\n animation: fadeInUp 0.8s ease-out 0.8s both;\n }\n \n .btn {\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n font-family: var(--font-mono);\n position: relative;\n overflow: hidden;\n }\n \n .btn::before {\n content: '';\n position: absolute;\n top: 50%; left: 50%;\n width: 0; height: 0;\n background: rgba(255,255,255,0.2);\n border-radius: 50%;\n transition: all 0.3s ease;\n transform: translate(-50%, -50%);\n }\n \n .btn:hover::before {\n width: 100px; height: 100px;\n }\n \n .btn-primary {\n background: var(--primary-color);\n color: var(--text-primary);\n border: 1px solid var(--border-color);\n }\n \n .btn-primary:hover:not(:disabled) {\n background: var(--primary-hover);\n transform: translateY(-2px);\n box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);\n }\n \n .btn-secondary {\n background: var(--bg-secondary);\n color: var(--text-secondary);\n border: 1px solid var(--border-color);\n }\n \n .btn-secondary:hover {\n background: rgba(255, 255, 255, 0.15);\n color: var(--text-primary);\n }\n \n .btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n \n .loading {\n display: none;\n align-items: center;\n justify-content: center;\n gap: 8px;\n margin-top: 16px;\n color: var(--text-secondary);\n font-size: 14px;\n }\n \n .spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--border-color);\n border-top: 2px solid var(--accent-color);\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n \n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n \n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n \n .success-state {\n text-align: center;\n padding: 40px 20px;\n animation: fadeInUp 0.8s ease-out both;\n }\n \n .success-icon {\n margin-bottom: 20px;\n color: var(--accent-color);\n animation: successPulse 2s ease-in-out infinite;\n }\n \n @keyframes successPulse {\n 0%, 100% { \n transform: scale(1);\n filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.3));\n }\n 50% { \n transform: scale(1.1);\n filter: drop-shadow(0 0 16px rgba(103, 233, 233, 0.6));\n }\n }\n \n .success-title {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 12px;\n }\n \n .success-message {\n color: var(--text-secondary);\n font-size: 16px;\n line-height: 1.5;\n }\n \n .selected-env-info {\n margin-top: 20px;\n padding: 16px;\n background: rgba(103, 233, 233, 0.1);\n border: 1px solid var(--accent-color);\n border-radius: 12px;\n display: flex;\n align-items: center;\n gap: 12px;\n }\n \n .env-label {\n color: var(--text-secondary);\n font-size: 14px;\n font-weight: 500;\n }\n \n .env-value {\n color: var(--accent-color);\n font-size: 16px;\n font-weight: 600;\n font-family: var(--font-mono);\n }\n </style>\n</head>\n<body>\n <div class="modal">\n <div class="header">\n <div class="header-left">\n <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" />\n <span class="title">CloudBase AI Toolkit</span>\n </div>\n <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">\n <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>\n </svg>\n GitHub\n </a>\n </div>\n\n <div class="content">\n <h1 class="content-title">选择云开发环境</h1>\n <p class="content-subtitle">请选择您要使用的云开发环境</p>\n \n <div class="env-list" id="envList">\n ${(e||[]).length>0?(e||[]).map((e,n)=>`\n <div class="env-item" onclick="selectEnv('${e.EnvId}', this)" style="animation-delay: ${.1*n}s;">\n <svg class="env-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>\n </svg>\n <div class="env-info">\n <div class="env-name">${e.EnvId}</div>\n <div class="env-alias">${e.Alias||"无别名"}</div>\n </div>\n </div>\n `).join(""):'\n <div class="empty-state">\n <h3 class="empty-title">暂无云开发环境</h3>\n <p class="empty-message">当前没有可用的云开发 CloudBase 环境,请新建后重新在 AI 对话中重试</p>\n <button class="btn btn-primary create-env-btn" onclick="createNewEnv()">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M12 5v14M5 12h14"/>\n </svg>\n 新建环境\n </button>\n </div>\n '}\n </div>\n \n <div class="actions">\n <button class="btn btn-secondary" onclick="cancel()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M18 6L6 18M6 6l12 12"/>\n </svg>\n 取消\n </button>\n <button class="btn btn-primary" id="confirmBtn" onclick="confirm()" disabled>\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M20 6L9 17l-5-5"/>\n </svg>\n 确认选择\n </button>\n </div>\n \n <div class="loading" id="loading">\n <div class="spinner"></div>\n <span>正在配置环境...</span>\n </div>\n \n <div class="success-state" id="successState" style="display: none;">\n <div class="success-icon">\n <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M20 6L9 17l-5-5"/>\n </svg>\n </div>\n <h2 class="success-title">环境配置成功!</h2>\n <p class="success-message">已成功选择云开发环境</p>\n <div class="selected-env-info">\n <span class="env-label">环境 ID:</span>\n <span class="env-value" id="selectedEnvDisplay"></span>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n let selectedEnvId = null;\n \n function selectEnv(envId, element) {\n console.log('=== 环境选择事件触发 ===');\n console.log('传入的envId:', envId);\n console.log('传入的element:', element);\n console.log('element类名:', element ? element.className : 'null');\n \n selectedEnvId = envId;\n console.log('设置selectedEnvId为:', selectedEnvId);\n \n // Remove selected class from all items\n const allItems = document.querySelectorAll('.env-item');\n console.log('找到的所有环境项数量:', allItems.length);\n allItems.forEach(item => {\n item.classList.remove('selected');\n });\n \n // Add selected class to current item\n if (element) {\n element.classList.add('selected');\n console.log('✅ 已添加selected样式到当前项');\n console.log('当前项的最终类名:', element.className);\n } else {\n console.error('❌ element为空,无法添加选中样式');\n }\n \n // Enable confirm button\n const confirmBtn = document.getElementById('confirmBtn');\n if (confirmBtn) {\n confirmBtn.disabled = false;\n console.log('✅ 确认按钮已启用');\n } else {\n console.error('❌ 找不到确认按钮');\n }\n }\n \n function confirm() {\n console.log('=== CONFIRM BUTTON CLICKED ===');\n console.log('selectedEnvId:', selectedEnvId);\n \n if (!selectedEnvId) {\n console.error('❌ 没有选择环境ID!');\n alert('请先选择一个环境');\n return;\n }\n \n console.log('✅ 环境ID验证通过,开始发送请求...');\n document.getElementById('loading').style.display = 'flex';\n document.getElementById('confirmBtn').disabled = true;\n \n const requestBody = {\n type: 'envId',\n data: selectedEnvId\n };\n \n console.log('📤 发送请求体:', JSON.stringify(requestBody, null, 2));\n \n fetch('/api/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestBody)\n }).then(response => {\n console.log('📥 收到响应状态:', response.status);\n console.log('📥 响应头:', [...response.headers.entries()]);\n return response.json();\n }).then(data => {\n console.log('📥 响应数据:', data);\n if (data.success) {\n console.log('✅ 请求成功,展示成功提示');\n // 隐藏选择区和按钮,仅展示成功提示\n document.getElementById('envList').style.display = 'none';\n document.querySelector('.actions').style.display = 'none';\n document.getElementById('loading').style.display = 'none';\n document.getElementById('successState').style.display = 'block';\n // 显示选中的环境 ID\n document.getElementById('selectedEnvDisplay').textContent = selectedEnvId;\n window.close();\n } else {\n console.error('❌ 请求失败:', data);\n alert('选择环境失败: ' + (data.error || '未知错误'));\n document.getElementById('loading').style.display = 'none';\n document.getElementById('confirmBtn').disabled = false;\n }\n }).catch(err => {\n console.error('❌ 网络请求错误:', err);\n alert('网络请求失败: ' + err.message);\n document.getElementById('loading').style.display = 'none';\n document.getElementById('confirmBtn').disabled = false;\n });\n }\n \n function createNewEnv() {\n const integrationIde = '${process.env.INTEGRATION_IDE||"AI Toolkit"}';\n const url = \`http://tcb.cloud.tencent.com/dev?from=\${encodeURIComponent(integrationIde)}\`;\n location.href = url;\n }\n \n function cancel() {\n fetch('/api/cancel', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' }\n }).then(() => {\n window.close();\n });\n }\n <\/script>\n</body>\n</html>`}getLogsHTML(e,n){return`\n<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>CloudBase MCP 调试日志</title>\n <style>\n @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');\n \n * { margin: 0; padding: 0; box-sizing: border-box; }\n :root {\n --primary-color: #1a1a1a;\n --primary-hover: #000000;\n --accent-color: #67E9E9;\n --accent-hover: #2BCCCC;\n --text-primary: #ffffff;\n --text-secondary: #a0a0a0;\n --border-color: rgba(255, 255, 255, 0.15);\n --bg-secondary: rgba(255, 255, 255, 0.08);\n --bg-glass: rgba(26, 26, 26, 0.95);\n --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);\n --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;\n --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);\n }\n \n body {\n font-family: var(--font-mono);\n background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);\n min-height: 100vh;\n padding: 20px;\n position: relative;\n overflow-x: hidden;\n overflow-y: auto;\n }\n \n /* Custom scrollbar styles */\n ::-webkit-scrollbar {\n width: 8px;\n }\n \n ::-webkit-scrollbar-track {\n background: rgba(255, 255, 255, 0.05);\n border-radius: 4px;\n }\n \n ::-webkit-scrollbar-thumb {\n background: var(--accent-color);\n border-radius: 4px;\n }\n \n ::-webkit-scrollbar-thumb:hover {\n background: var(--accent-hover);\n }\n \n body::before {\n content: '';\n position: fixed;\n top: 0; left: 0; right: 0; bottom: 0;\n background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/