UNPKG

aem-mcp-server

Version:
2 lines (1 loc) 20.8 kB
"use strict";import{getAEMConfig as j,isValidContentPath as g,isValidLocale as P}from"./aem.config.js";import{AEM_ERROR_CODES as h,createAEMError as d,createSuccessResponse as i,handleAEMHttpError as u,safeExecute as p,validateComponentOperation as b}from"./aem.errors.js";import{AEMFetch as w}from"./aem.fetch.js";export class AEMConnector{isInitialized;config;aemConfig;fetch;constructor(e){this.isInitialized=!1,this.config=this.loadConfig(e),this.aemConfig=j({}),this.fetch=new w({host:this.config.aem.host,username:this.config.aem.serviceUser.username,password:this.config.aem.serviceUser.password,timeout:this.aemConfig.queries.timeoutMs})}async init(){try{await this.fetch.init(),this.isInitialized=!0}catch{this.isInitialized=!1}}loadConfig(e={}){return{aem:{host:e.host||"http://localhost:4502",author:e.host||"http://localhost:4502",publish:"http://localhost:4503",serviceUser:{username:e.user||"admin",password:e.pass||"admin"},endpoints:{content:"/content",dam:"/content/dam",query:"/bin/querybuilder.json",crxde:"/crx/de",jcr:""}},mcp:{name:"AEM MCP Server",version:"1.0.0"}}}async testConnection(){try{console.log("Testing AEM connection to:",this.config.aem.host);const e=`${this.config.aem.host}/libs/granite/core/content/login.html`,t=await this.fetch.get(e,{timeout:5e3});return console.log("\u2705 AEM connection successful! Status:",t.status),!0}catch(e){return console.error("\u274C AEM connection failed:",e.message),!1}}async validateComponent(e){return p(async()=>{const t=e.pagePath||e.page_path,{locale:n,component:s,props:a}=e;if(b(n,t,s,a),!P(n,this.aemConfig))throw d(h.INVALID_LOCALE,`Locale '${n}' is not supported`,{locale:n,allowedLocales:this.aemConfig.validation.allowedLocales});if(!g(t,this.aemConfig))throw d(h.INVALID_PATH,`Path '${t}' is not within allowed content roots`,{path:t,allowedRoots:Object.values(this.aemConfig.contentPaths)});const r=`${t}.json`,o=await this.fetch.get(r,{params:{":depth":"2"},timeout:this.aemConfig.queries.timeoutMs}),c=this.validateComponentProps(o.data,s,a);return i({message:"Component validation completed successfully",pageData:o.data,component:s,locale:n,validation:c,configUsed:{allowedLocales:this.aemConfig.validation.allowedLocales}},"validateComponent")},"validateComponent")}validateComponentProps(e,t,n){const s=[],a=[];return t==="text"&&!n.text&&!n.richText&&s.push("Text component should have text or richText property"),t==="image"&&!n.fileReference&&!n.src&&a.push("Image component requires fileReference or src property"),{valid:a.length===0,errors:a,warnings:s,componentType:t,propsValidated:Object.keys(n).length}}async updateComponent(e){return p(async()=>{if(!e.componentPath||typeof e.componentPath!="string")throw d(h.INVALID_PARAMETERS,"Component path is required and must be a string");if(!e.properties||typeof e.properties!="object")throw d(h.INVALID_PARAMETERS,"Properties are required and must be an object");if(!g(e.componentPath,this.aemConfig))throw d(h.INVALID_PATH,`Component path '${e.componentPath}' is not within allowed content roots`,{path:e.componentPath,allowedRoots:Object.values(this.aemConfig.contentPaths)});const t=`${e.componentPath}.json`,n=await this.fetch.get(t);try{await n.json()}catch(o){throw o.response?.status===404?d(h.COMPONENT_NOT_FOUND,`Component not found at path: ${e.componentPath}`,{componentPath:e.componentPath}):u(o,"updateComponent")}const s=new URLSearchParams;Object.entries(e.properties).forEach(([o,c])=>{c==null?s.append(`${o}@Delete`,""):Array.isArray(c)?c.forEach(l=>{s.append(`${o}`,l.toString())}):typeof c=="object"?s.append(o,JSON.stringify(c)):s.append(o,c.toString())});const a=await this.fetch.post(e.componentPath,s),r=await this.fetch.get(`${e.componentPath}.json`);return i({message:"Component updated successfully",path:e.componentPath,properties:e.properties,updatedProperties:r.data,response:a.data,verification:{success:!0,propertiesChanged:Object.keys(e.properties).length,timestamp:new Date().toISOString()}},"updateComponent")},"updateComponent")}async undoChanges(e){return i({message:"undoChanges is not implemented. Please use AEM version history for undo/rollback.",request:e,timestamp:new Date().toISOString()},"undoChanges")}async scanPageComponents(e){return p(async()=>{const t=`${e}.infinity.json`,n=await this.fetch.get(t),s=[],a=(r,o)=>{!r||typeof r!="object"||(r["sling:resourceType"]&&s.push({path:o,resourceType:r["sling:resourceType"],properties:{...r}}),Object.entries(r).forEach(([c,l])=>{if(typeof l=="object"&&l!==null&&!c.startsWith("rep:")&&!c.startsWith("oak:")){const m=o?`${o}/${c}`:c;a(l,m)}}))};return n.data["jcr:content"]?a(n.data["jcr:content"],"jcr:content"):a(n.data,e),i({pagePath:e,components:s,totalComponents:s.length},"scanPageComponents")},"scanPageComponents")}async fetchSites(){return p(async()=>{const t=await this.fetch.get("/content.json",{":depth":"2"}),n=[];return Object.entries(t).forEach(([s,a])=>{s.startsWith("jcr:")||s.startsWith("sling:")||a&&typeof a=="object"&&a["jcr:content"]&&n.push({name:s,path:`/content/${s}`,title:a["jcr:content"]["jcr:title"]||s,template:a["jcr:content"]["cq:template"],lastModified:a["jcr:content"]["cq:lastModified"]})}),i({sites:n,totalCount:n.length},"fetchSites")},"fetchSites")}async fetchLanguageMasters(e){return p(async()=>{const t=`/content/${e}.json`,n=await this.fetch.get(t,{":depth":"3"}),s=[];return Object.entries(n).forEach(([a,r])=>{a.startsWith("jcr:")||a.startsWith("sling:")||r&&typeof r=="object"&&r["jcr:content"]&&s.push({name:a,path:`/content/${a}`,title:r["jcr:content"]["jcr:title"]||a,language:r["jcr:content"]["jcr:language"]||"en"})}),i({site:e,languageMasters:s},"fetchLanguageMasters")},"fetchLanguageMasters")}async fetchAvailableLocales(e,t){return p(async()=>{const n=`${t}.json`,s=await this.fetch.get(n,{":depth":"2"}),a=[];return Object.entries(s).forEach(([r,o])=>{r.startsWith("jcr:")||r.startsWith("sling:")||o&&typeof o=="object"&&a.push({name:r,title:o["jcr:content"]?.["jcr:title"]||r,language:o["jcr:content"]?.["jcr:language"]||r})}),i({site:e,languageMasterPath:t,availableLocales:a},"fetchAvailableLocales")},"fetchAvailableLocales")}async replicateAndPublish(e,t,n){return p(async()=>i({message:"Replication simulated",selectedLocales:e,componentData:t,localizedOverrides:n},"replicateAndPublish"),"replicateAndPublish")}async getAllTextContent(e){return p(async()=>{const t=`${e}.infinity.json`,n=await this.fetch.get(t),s=[],a=(r,o)=>{!r||typeof r!="object"||((r.text||r["jcr:title"]||r["jcr:description"])&&s.push({path:o,title:r["jcr:title"],text:r.text,description:r["jcr:description"]}),Object.entries(r).forEach(([c,l])=>{if(typeof l=="object"&&l!==null&&!c.startsWith("rep:")&&!c.startsWith("oak:")){const m=o?`${o}/${c}`:c;a(l,m)}}))};return n["jcr:content"]?a(n["jcr:content"],"jcr:content"):a(n,e),i({pagePath:e,textContent:s},"getAllTextContent")},"getAllTextContent")}async getPageTextContent(e){return p(async()=>this.getAllTextContent(e),"getPageTextContent")}async getPageImages(e){return p(async()=>{const t=`${e}.infinity.json`,n=await this.fetch.get(t),s=[],a=(r,o)=>{!r||typeof r!="object"||((r.fileReference||r.src)&&s.push({path:o,fileReference:r.fileReference,src:r.src,alt:r.alt||r.altText,title:r["jcr:title"]||r.title}),Object.entries(r).forEach(([c,l])=>{if(typeof l=="object"&&l!==null&&!c.startsWith("rep:")&&!c.startsWith("oak:")){const m=o?`${o}/${c}`:c;a(l,m)}}))};return n["jcr:content"]?a(n["jcr:content"],"jcr:content"):a(n,e),i({pagePath:e,images:s},"getPageImages")},"getPageImages")}async updateImagePath(e,t){return p(async()=>this.updateComponent({componentPath:e,properties:{fileReference:t}}),"updateImagePath")}async getPageContent(e){return p(async()=>{const t=`${e}.infinity.json`,n=await this.fetch.get(t);return i({pagePath:e,content:n},"getPageContent")},"getPageContent")}async listChildren(e,t=1){return p(async()=>{try{const n=await this.fetch.get(`${e}.${t}.json`),s=[];return n&&typeof n=="object"&&Object.entries(n).forEach(([a,r])=>{if(!(a.startsWith("jcr:")||a.startsWith("sling:")||a.startsWith("cq:")||a.startsWith("rep:")||a.startsWith("oak:")||a==="jcr:content")&&r&&typeof r=="object"){const o=`${e}/${a}`;s.push({name:a,path:o,primaryType:r["jcr:primaryType"]||"nt:unstructured",title:r["jcr:content"]?.["jcr:title"]||r["jcr:title"]||a,lastModified:r["jcr:content"]?.["cq:lastModified"]||r["cq:lastModified"],resourceType:r["jcr:content"]?.["sling:resourceType"]||r["sling:resourceType"]})}}),s}catch(n){if(n.response?.status===404||n.response?.status===403)return((await this.fetch.get("/bin/querybuilder.json",{path:e,type:"cq:Page","p.nodedepth":"1","p.limit":"1000","p.hits":"full"})).hits||[]).map(a=>({name:a.name||a.path?.split("/").pop(),path:a.path,primaryType:a["jcr:primaryType"]||"cq:Page",title:a["jcr:content/jcr:title"]||a.title||a.name,lastModified:a["jcr:content/cq:lastModified"],resourceType:a["jcr:content/sling:resourceType"]}));throw n}},"listChildren")}async listPages(e,t=1,n=20){return p(async()=>{try{const s=await this.fetch.get(`${e}.${t}.json`),a=[],r=(o,c,l)=>{l>t||a.length>=n||Object.entries(o).forEach(([m,f])=>{if(!(a.length>=n)&&!(m.startsWith("jcr:")||m.startsWith("sling:")||m.startsWith("cq:")||m.startsWith("rep:")||m.startsWith("oak:"))&&f&&typeof f=="object"){const y=`${c}/${m}`;f["jcr:primaryType"]==="cq:Page"&&a.push({name:m,path:y,primaryType:"cq:Page",title:f["jcr:content"]?.["jcr:title"]||m,template:f["jcr:content"]?.["cq:template"],lastModified:f["jcr:content"]?.["cq:lastModified"],lastModifiedBy:f["jcr:content"]?.["cq:lastModifiedBy"],resourceType:f["jcr:content"]?.["sling:resourceType"],type:"page"}),l<t&&r(f,y,l+1)}})};return s&&typeof s=="object"&&r(s,e,0),i({siteRoot:e,pages:a,pageCount:a.length,depth:t,limit:n,totalChildrenScanned:a.length},"listPages")}catch(s){if(console.warn("JSON API failed, falling back to QueryBuilder:",s.message),s.response?.status===404||s.response?.status===403){const a=await this.fetch.get("/bin/querybuilder.json",{path:e,type:"cq:Page","p.nodedepth":t.toString(),"p.limit":n.toString(),"p.hits":"full"}),r=(a.hits||[]).map(o=>({name:o.name||o.path?.split("/").pop(),path:o.path,primaryType:"cq:Page",title:o["jcr:content/jcr:title"]||o.title||o.name,template:o["jcr:content/cq:template"],lastModified:o["jcr:content/cq:lastModified"],lastModifiedBy:o["jcr:content/cq:lastModifiedBy"],resourceType:o["jcr:content/sling:resourceType"],type:"page"}));return i({siteRoot:e,pages:r,pageCount:r.length,depth:t,limit:n,totalChildrenScanned:a.total||r.length,fallbackUsed:"QueryBuilder"},"listPages")}throw s}},"listPages")}async executeJCRQuery(e,t=20){return p(async()=>{if(!e||e.trim().length===0)throw new Error("Query is required and must be a non-empty string. Note: Only QueryBuilder fulltext is supported, not JCR SQL2.");const n=e.toLowerCase();if(/drop|delete|update|insert|exec|script|\.|<script/i.test(n)||e.length>1e3)throw new Error("Query contains potentially unsafe patterns or is too long");const s=await this.fetch.get("/bin/querybuilder.json",{path:"/content",type:"cq:Page",fulltext:e,"p.limit":t});return{query:e,results:s.hits||[],total:s.total||0,limit:t}},"executeJCRQuery")}async getPageProperties(e){return p(async()=>{const t=`${e}/jcr:content.json`,n=await this.fetch.get(t),s={title:n["jcr:title"],description:n["jcr:description"],template:n["cq:template"],lastModified:n["cq:lastModified"],lastModifiedBy:n["jcr:createdBy"],created:n["jcr:created"],createdBy:n["jcr:createdBy"],primaryType:n["jcr:primaryType"],resourceType:n["sling:resourceType"],tags:n["cq:tags"]||[],properties:n};return i({pagePath:e,properties:s},"getPageProperties")},"getPageProperties")}async searchContent(e){return p(async()=>{const t=await this.fetch.get(this.config.aem.endpoints.query,e);return i({params:e,results:t.hits||[],total:t.total||0,rawResponse:t},"searchContent")},"searchContent")}async getAssetMetadata(e){return p(async()=>{const t=`${e}.json`,n=await this.fetch.get(t),s=n["jcr:content"]?.metadata||{};return i({assetPath:e,metadata:s,fullData:n},"getAssetMetadata")},"getAssetMetadata")}async createPage(e){return p(async()=>{const{parentPath:t,title:n,template:s,name:a,properties:r}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid parent path: ${String(t)}`,{parentPath:t});const o=a||n.replace(/\s+/g,"-").toLowerCase(),c=`${t}/${o}`;return await this.fetch.post(c,{"jcr:primaryType":"cq:Page","jcr:title":n,"cq:template":s,...r}),i({success:!0,pagePath:c,title:n,template:s,properties:r,timestamp:new Date().toISOString()},"createPage")},"createPage")}async deletePage(e){return p(async()=>{const{pagePath:t}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid page path: ${String(t)}`,{pagePath:t});let n=!1;try{await this.fetch.delete(t),n=!0}catch{try{await this.fetch.post("/bin/wcmcommand",{cmd:"deletePage",path:t,force:e.force?"true":"false"}),n=!0}catch{try{await this.fetch.post(t,{":operation":"delete"}),n=!0}catch(r){throw r}}}return i({success:n,deletedPath:t,timestamp:new Date().toISOString()},"deletePage")},"deletePage")}async createComponent(e){return p(async()=>{const{pagePath:t,componentPath:n,componentType:s,resourceType:a,properties:r={},name:o}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid page path: ${String(t)}`,{pagePath:t});const c=o||`${s}_${Date.now()}`,l=n||`${t}/jcr:content/${c}`;return await this.fetch.post(l,{"jcr:primaryType":"nt:unstructured","sling:resourceType":a,...r,":operation":"import",":contentType":"json",":replace":"true"}),i({success:!0,componentPath:l,componentType:s,resourceType:a,properties:r,timestamp:new Date().toISOString()},"createComponent")},"createComponent")}async deleteComponent(e){return p(async()=>{const{componentPath:t}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid component path: ${String(t)}`,{componentPath:t});let n=!1;try{await this.fetch.delete(t),n=!0}catch{try{await this.fetch.post(t,{":operation":"delete"}),n=!0}catch(a){throw a}}return i({success:n,deletedPath:t,timestamp:new Date().toISOString()},"deleteComponent")},"deleteComponent")}async unpublishContent(e){return p(async()=>{const{contentPaths:t,unpublishTree:n=!1}=e;if(!t||Array.isArray(t)&&t.length===0)throw d(h.INVALID_PARAMETERS,"Content paths array is required and cannot be empty",{contentPaths:t});const s=[];for(const a of Array.isArray(t)?t:[t])try{const r=new URLSearchParams;r.append("cmd","Deactivate"),r.append("path",a),r.append("ignoredeactivated","false"),r.append("onlymodified","false"),n&&r.append("deep","true");const o=await this.fetch.post("/bin/replicate.json",r);s.push({path:a,success:!0,response:o})}catch(r){s.push({path:a,success:!1,error:r.message})}return i({success:s.every(a=>a.success),results:s,unpublishedPaths:t,unpublishTree:n,timestamp:new Date().toISOString()},"unpublishContent")},"unpublishContent")}async activatePage(e){return p(async()=>{const{pagePath:t,activateTree:n=!1}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid page path: ${String(t)}`,{pagePath:t});try{const s=new URLSearchParams;s.append("cmd","Activate"),s.append("path",t),s.append("ignoredeactivated","false"),s.append("onlymodified","false"),n&&s.append("deep","true");const a=await this.fetch.post("/bin/replicate.json",s);return i({success:!0,activatedPath:t,activateTree:n,response:a,timestamp:new Date().toISOString()},"activatePage")}catch(s){try{const a=await this.fetch.post("/bin/wcmcommand",{cmd:"activate",path:t,ignoredeactivated:!1,onlymodified:!1});return i({success:!0,activatedPath:t,activateTree:n,response:a,fallbackUsed:"WCM Command",timestamp:new Date().toISOString()},"activatePage")}catch{throw u(s,"activatePage")}}},"activatePage")}async deactivatePage(e){return p(async()=>{const{pagePath:t,deactivateTree:n=!1}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid page path: ${String(t)}`,{pagePath:t});try{const s=new URLSearchParams;s.append("cmd","Deactivate"),s.append("path",t),s.append("ignoredeactivated","false"),s.append("onlymodified","false"),n&&s.append("deep","true");const a=await this.fetch.post("/bin/replicate.json",s);return i({success:!0,deactivatedPath:t,deactivateTree:n,response:a,timestamp:new Date().toISOString()},"deactivatePage")}catch(s){try{const a=await this.fetch.post("/bin/wcmcommand",{cmd:"deactivate",path:t,ignoredeactivated:!1,onlymodified:!1});return i({success:!0,deactivatedPath:t,deactivateTree:n,response:a,fallbackUsed:"WCM Command",timestamp:new Date().toISOString()},"deactivatePage")}catch{throw u(s,"deactivatePage")}}},"deactivatePage")}async uploadAsset(e){return p(async()=>{const{parentPath:t,fileName:n,fileContent:s,mimeType:a,metadata:r={}}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid parent path: ${String(t)}`,{parentPath:t});const o=`${t}/${n}`;try{const c=new URLSearchParams;typeof s=="string"?c.append("file",s):c.append("file",s.toString()),c.append("fileName",n),c.append(":operation","import"),c.append(":contentType","json"),c.append(":replace","true"),c.append("jcr:primaryType","dam:Asset"),a&&c.append("jcr:content/jcr:mimeType",a),Object.entries(r).forEach(([f,y])=>{c.append(`jcr:content/metadata/${f}`,String(y))});const l=await this.fetch.post(o,c),m=await this.fetch.get(`${o}.json`);return i({success:!0,assetPath:o,fileName:n,mimeType:a,metadata:r,uploadResponse:l,assetData:m,timestamp:new Date().toISOString()},"uploadAsset")}catch(c){try{const l=await this.fetch.post("/api/assets"+t,{fileName:n,fileContent:s,mimeType:a,metadata:r});return i({success:!0,assetPath:o,fileName:n,mimeType:a,metadata:r,uploadResponse:l,fallbackUsed:"DAM API",timestamp:new Date().toISOString()},"uploadAsset")}catch{throw u(c,"uploadAsset")}}},"uploadAsset")}async updateAsset(e){return p(async()=>{const{assetPath:t,metadata:n,fileContent:s,mimeType:a}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid asset path: ${String(t)}`,{assetPath:t});const r=new URLSearchParams;s&&(r.append("file",s),a&&r.append("jcr:content/jcr:mimeType",a)),n&&typeof n=="object"&&Object.entries(n).forEach(([o,c])=>{r.append(`jcr:content/metadata/${o}`,String(c))});try{const o=await this.fetch.post(t,r),c=await this.fetch.get(`${t}.json`);return i({success:!0,assetPath:t,updatedMetadata:n,updateResponse:o,assetData:c,timestamp:new Date().toISOString()},"updateAsset")}catch(o){throw u(o,"updateAsset")}},"updateAsset")}async deleteAsset(e){return p(async()=>{const{assetPath:t,force:n=!1}=e;if(!g(t,this.aemConfig))throw d(h.INVALID_PARAMETERS,`Invalid asset path: ${String(t)}`,{assetPath:t});return await this.fetch.delete(t),i({success:!0,deletedPath:t,force:n,timestamp:new Date().toISOString()},"deleteAsset")},"deleteAsset")}async getTemplateStructure(e){return p(async()=>{try{const t=await this.fetch.get(`${e}.infinity.json`),n={path:e,properties:t["jcr:content"]||{},policies:t["jcr:content"]?.policies||{},structure:t["jcr:content"]?.structure||{},initialContent:t["jcr:content"]?.initial||{},allowedComponents:[],allowedPaths:t["jcr:content"]?.allowedPaths||[]},s=(a,r="")=>{if(!(!a||typeof a!="object")){if(a.components){const o=Object.keys(a.components);n.allowedComponents.push(...o)}Object.entries(a).forEach(([o,c])=>{typeof c=="object"&&c!==null&&!o.startsWith("jcr:")&&s(c,r?`${r}/${o}`:o)})}};return s(n.policies),n.allowedComponents=[...new Set(n.allowedComponents)],i({templatePath:e,structure:n,fullData:t},"getTemplateStructure")}catch(t){throw u(t,"getTemplateStructure")}},"getTemplateStructure")}async bulkUpdateComponents(e){return p(async()=>{const{updates:t,validateFirst:n=!0,continueOnError:s=!1}=e;if(!Array.isArray(t)||t.length===0)throw d(h.INVALID_PARAMETERS,"Updates array is required and cannot be empty");const a=[];if(n)for(const o of t)try{await this.fetch.get(`${o.componentPath}.json`)}catch(c){if(c.response?.status===404&&(a.push({componentPath:o.componentPath,success:!1,error:`Component not found: ${o.componentPath}`,phase:"validation"}),!s))return i({success:!1,message:"Bulk update failed during validation phase",results:a,totalUpdates:t.length,successfulUpdates:0},"bulkUpdateComponents")}let r=0;for(const o of t)try{const c=await this.updateComponent({componentPath:o.componentPath,properties:o.properties});a.push({componentPath:o.componentPath,success:!0,result:c,phase:"update"}),r++}catch(c){if(a.push({componentPath:o.componentPath,success:!1,error:c.message,phase:"update"}),!s)break}return i({success:r===t.length,message:`Bulk update completed: ${r}/${t.length} successful`,results:a,totalUpdates:t.length,successfulUpdates:r,failedUpdates:t.length-r},"bulkUpdateComponents")},"bulkUpdateComponents")}async getNodeContent(e,t=1){return p(async()=>{const n=`${e}.json`,s=await this.fetch.get(n,{":depth":t.toString()});return{path:e,depth:t,content:s,timestamp:new Date().toISOString()}},"getNodeContent")}}